@typed/fx 2.0.0-beta.0 → 2.0.0-beta.2
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 +24 -1
- package/dist/Fx/combinators/additive.d.ts +94 -0
- package/dist/Fx/combinators/additive.d.ts.map +1 -0
- package/dist/Fx/combinators/additive.js +92 -0
- package/dist/Fx/combinators/catch.d.ts +61 -0
- package/dist/Fx/combinators/catch.d.ts.map +1 -1
- package/dist/Fx/combinators/catch.js +54 -0
- package/dist/Fx/combinators/changesWithEffect.d.ts +20 -0
- package/dist/Fx/combinators/changesWithEffect.d.ts.map +1 -0
- package/dist/Fx/combinators/changesWithEffect.js +28 -0
- package/dist/Fx/combinators/dropUntil.d.ts +29 -0
- package/dist/Fx/combinators/dropUntil.d.ts.map +1 -0
- package/dist/Fx/combinators/dropUntil.js +23 -0
- package/dist/Fx/combinators/flatMapConcurrently.d.ts.map +1 -1
- package/dist/Fx/combinators/flatMapConcurrently.js +3 -2
- package/dist/Fx/combinators/index.d.ts +10 -0
- package/dist/Fx/combinators/index.d.ts.map +1 -1
- package/dist/Fx/combinators/index.js +10 -0
- package/dist/Fx/combinators/keyed.d.ts +1 -1
- package/dist/Fx/combinators/keyed.d.ts.map +1 -1
- package/dist/Fx/combinators/mapBoth.d.ts +21 -0
- package/dist/Fx/combinators/mapBoth.d.ts.map +1 -0
- package/dist/Fx/combinators/mapBoth.js +14 -0
- package/dist/Fx/combinators/mapError.d.ts +17 -0
- package/dist/Fx/combinators/mapError.d.ts.map +1 -0
- package/dist/Fx/combinators/mapError.js +16 -0
- package/dist/Fx/combinators/provide.d.ts +34 -1
- package/dist/Fx/combinators/provide.d.ts.map +1 -1
- package/dist/Fx/combinators/provide.js +27 -0
- package/dist/Fx/combinators/result.d.ts +23 -0
- package/dist/Fx/combinators/result.d.ts.map +1 -0
- package/dist/Fx/combinators/result.js +32 -0
- package/dist/Fx/combinators/scan.d.ts +33 -0
- package/dist/Fx/combinators/scan.d.ts.map +1 -0
- package/dist/Fx/combinators/scan.js +38 -0
- package/dist/Fx/combinators/skip.d.ts +13 -0
- package/dist/Fx/combinators/skip.d.ts.map +1 -1
- package/dist/Fx/combinators/skip.js +11 -0
- package/dist/Fx/combinators/skipWhile.d.ts +49 -0
- package/dist/Fx/combinators/skipWhile.d.ts.map +1 -0
- package/dist/Fx/combinators/skipWhile.js +66 -0
- package/dist/Fx/combinators/slice.d.ts +13 -0
- package/dist/Fx/combinators/slice.d.ts.map +1 -1
- package/dist/Fx/combinators/slice.js +11 -0
- package/dist/Fx/combinators/take.d.ts +13 -0
- package/dist/Fx/combinators/take.d.ts.map +1 -1
- package/dist/Fx/combinators/take.js +11 -0
- package/dist/Fx/combinators/takeUntil.d.ts +14 -0
- package/dist/Fx/combinators/takeUntil.d.ts.map +1 -1
- package/dist/Fx/combinators/takeUntil.js +14 -0
- package/dist/Fx/combinators/takeWhile.d.ts +29 -0
- package/dist/Fx/combinators/takeWhile.d.ts.map +1 -0
- package/dist/Fx/combinators/takeWhile.js +23 -0
- package/dist/Fx/combinators/zip.d.ts +75 -0
- package/dist/Fx/combinators/zip.d.ts.map +1 -0
- package/dist/Fx/combinators/zip.js +100 -0
- package/dist/Fx/constructors/at.d.ts +2 -2
- package/dist/Fx/constructors/at.d.ts.map +1 -1
- package/dist/Fx/constructors/periodic.d.ts +1 -1
- package/dist/Fx/constructors/periodic.d.ts.map +1 -1
- package/dist/Push/Push.d.ts +64 -1
- package/dist/Push/Push.d.ts.map +1 -1
- package/dist/Push/Push.js +57 -0
- package/dist/RefSubject/RefArray.d.ts.map +1 -1
- package/dist/RefSubject/RefArray.js +2 -1
- package/dist/RefSubject/RefChunk.d.ts.map +1 -1
- package/dist/RefSubject/RefChunk.js +2 -1
- package/dist/RefSubject/RefDateTime.d.ts +4 -4
- package/dist/RefSubject/RefDateTime.d.ts.map +1 -1
- package/dist/RefSubject/RefHashMap.d.ts.map +1 -1
- package/dist/RefSubject/RefHashMap.js +5 -1
- package/dist/RefSubject/RefIterable.d.ts +1 -1
- package/dist/RefSubject/RefIterable.d.ts.map +1 -1
- package/dist/RefSubject/RefIterable.js +6 -1
- package/dist/RefSubject/RefRecord.d.ts.map +1 -1
- package/dist/RefSubject/RefRecord.js +3 -2
- package/dist/RefSubject/RefSubject.d.ts +48 -1
- package/dist/RefSubject/RefSubject.d.ts.map +1 -1
- package/dist/RefSubject/RefSubject.js +80 -1
- package/dist/RefSubject/RefTrie.d.ts +7 -7
- package/dist/RefSubject/RefTrie.d.ts.map +1 -1
- package/dist/RefSubject/RefTrie.js +8 -3
- package/dist/Sink/combinators.d.ts +57 -0
- package/dist/Sink/combinators.d.ts.map +1 -1
- package/dist/Sink/combinators.js +104 -1
- package/dist/Versioned/Versioned.d.ts +30 -0
- package/dist/Versioned/Versioned.d.ts.map +1 -1
- package/dist/Versioned/Versioned.js +18 -0
- package/package.json +10 -6
- package/src/Fx/combinators/additive.ts +142 -0
- package/src/Fx/combinators/catch.ts +256 -0
- package/src/Fx/combinators/changesWithEffect.ts +66 -0
- package/src/Fx/combinators/dropUntil.ts +47 -0
- package/src/Fx/combinators/flatMapConcurrently.ts +5 -2
- package/src/Fx/combinators/index.ts +10 -0
- package/src/Fx/combinators/keyed.ts +2 -2
- package/src/Fx/combinators/mapBoth.ts +40 -0
- package/src/Fx/combinators/mapError.ts +28 -0
- package/src/Fx/combinators/provide.ts +63 -1
- package/src/Fx/combinators/result.ts +39 -0
- package/src/Fx/combinators/scan.ts +82 -0
- package/src/Fx/combinators/skip.ts +21 -0
- package/src/Fx/combinators/skipWhile.ts +100 -0
- package/src/Fx/combinators/slice.ts +23 -0
- package/src/Fx/combinators/take.ts +21 -0
- package/src/Fx/combinators/takeUntil.ts +38 -0
- package/src/Fx/combinators/takeWhile.ts +47 -0
- package/src/Fx/combinators/zip.ts +175 -0
- package/src/Fx/constructors/at.ts +3 -3
- package/src/Fx/constructors/periodic.ts +1 -1
- package/src/Fx.additive-combinators.test.ts +126 -0
- package/src/Fx.catch-additive.test.ts +206 -0
- package/src/Fx.catch.test.ts +1 -2
- package/src/Fx.dropUntil.test.ts +61 -0
- package/src/Fx.lifecycle.test.ts +1 -2
- package/src/Fx.mapError-mapBoth.test.ts +101 -0
- package/src/Fx.provide-combinators.test.ts +94 -0
- package/src/Fx.result-changesWithEffect.test.ts +112 -0
- package/src/Fx.scan.test.ts +73 -0
- package/src/Fx.takeWhile-skipWhile.test.ts +84 -0
- package/src/Fx.zip-merge-additive.test.ts +171 -0
- package/src/Fx.zip.test.ts +133 -0
- package/src/Push/Push.ts +170 -1
- package/src/Push.additive.test.ts +256 -0
- package/src/RefSubject/RefArray.ts +4 -1
- package/src/RefSubject/RefChunk.ts +2 -1
- package/src/RefSubject/RefDateTime.ts +6 -6
- package/src/RefSubject/RefHashMap.ts +10 -1
- package/src/RefSubject/RefIterable.ts +11 -2
- package/src/RefSubject/RefRecord.ts +9 -2
- package/src/RefSubject/RefSubject.ts +108 -9
- package/src/RefSubject/RefTrie.ts +19 -10
- package/src/RefSubject.additive-parity.test.ts +101 -0
- package/src/Sink/combinators.ts +123 -1
- package/src/Sink.combinators.test.ts +88 -0
- package/src/Sink.reduce-collect-head-last.test.ts +107 -0
- package/src/Versioned/Versioned.ts +76 -0
- package/src/Versioned.filterMap.test.ts +91 -0
- package/tsconfig.json +0 -6
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Exit from "effect/Exit";
|
|
4
|
+
import { concat, merge } from "./Fx/combinators/additive.js";
|
|
5
|
+
import { Fx } from "./index.js";
|
|
6
|
+
|
|
7
|
+
describe("Fx additive combinators", () => {
|
|
8
|
+
describe("merge (binary)", () => {
|
|
9
|
+
it("emits values from both streams and completes when both complete", () =>
|
|
10
|
+
Effect.gen(function* () {
|
|
11
|
+
const fx1 = Fx.fromIterable([1, 2]);
|
|
12
|
+
const fx2 = Fx.fromIterable([3, 4]);
|
|
13
|
+
const merged = merge(fx1, fx2);
|
|
14
|
+
const result = yield* Fx.collectAll(merged);
|
|
15
|
+
assert.strictEqual(result.length, 4);
|
|
16
|
+
assert.deepStrictEqual(
|
|
17
|
+
[...result].sort((a, b) => a - b),
|
|
18
|
+
[1, 2, 3, 4],
|
|
19
|
+
);
|
|
20
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
21
|
+
|
|
22
|
+
it("completes when both streams are empty", () =>
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const merged = merge(Fx.fromIterable([]), Fx.fromIterable([]));
|
|
25
|
+
const result = yield* Fx.collectAll(merged);
|
|
26
|
+
assert.deepStrictEqual(result, []);
|
|
27
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
28
|
+
|
|
29
|
+
it("fails when first stream fails", () =>
|
|
30
|
+
Effect.gen(function* () {
|
|
31
|
+
const failFx = Fx.fail("err" as const);
|
|
32
|
+
const goodFx = Fx.fromIterable([1, 2]);
|
|
33
|
+
const merged = merge(failFx, goodFx);
|
|
34
|
+
const exit = yield* Effect.exit(Fx.collectAll(merged));
|
|
35
|
+
assert(Exit.isFailure(exit));
|
|
36
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
37
|
+
|
|
38
|
+
it("fails when second stream fails", () =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const goodFx = Fx.fromIterable([1, 2]);
|
|
41
|
+
const failFx = Fx.fail("err" as const);
|
|
42
|
+
const merged = merge(goodFx, failFx);
|
|
43
|
+
const exit = yield* Effect.exit(Fx.collectAll(merged));
|
|
44
|
+
assert(Exit.isFailure(exit));
|
|
45
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("concat", () => {
|
|
49
|
+
it("emits first stream then second stream in order", () =>
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
const fx1 = Fx.fromIterable([1, 2]);
|
|
52
|
+
const fx2 = Fx.fromIterable([3, 4]);
|
|
53
|
+
const concatenated = concat(fx1, fx2);
|
|
54
|
+
const result = yield* Fx.collectAll(concatenated);
|
|
55
|
+
assert.deepStrictEqual(result, [1, 2, 3, 4]);
|
|
56
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
57
|
+
|
|
58
|
+
it("completes when second stream completes", () =>
|
|
59
|
+
Effect.gen(function* () {
|
|
60
|
+
const concatenated = concat(Fx.fromIterable([1]), Fx.fromIterable([2, 3]));
|
|
61
|
+
const result = yield* Fx.collectAll(concatenated);
|
|
62
|
+
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
63
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
64
|
+
|
|
65
|
+
it("fails when first stream fails (second never runs)", () =>
|
|
66
|
+
Effect.gen(function* () {
|
|
67
|
+
const concatenated = concat(Fx.fail("err" as const), Fx.fromIterable([1, 2]));
|
|
68
|
+
const exit = yield* Effect.exit(Fx.collectAll(concatenated));
|
|
69
|
+
assert(Exit.isFailure(exit));
|
|
70
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
71
|
+
|
|
72
|
+
it("fails when second stream fails after first completed", () =>
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const concatenated = concat(Fx.fromIterable([1, 2]), Fx.fail("err" as const));
|
|
75
|
+
const exit = yield* Effect.exit(Fx.collectAll(concatenated));
|
|
76
|
+
assert(Exit.isFailure(exit));
|
|
77
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
78
|
+
|
|
79
|
+
it("completes with first stream only when second is empty", () =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const concatenated = concat(Fx.fromIterable([1, 2]), Fx.fromIterable([]));
|
|
82
|
+
const result = yield* Fx.collectAll(concatenated);
|
|
83
|
+
assert.deepStrictEqual(result, [1, 2]);
|
|
84
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
85
|
+
});
|
|
86
|
+
it("takeEffect uses count from Effect", () =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(Fx.takeEffect(Effect.succeed(2)));
|
|
89
|
+
const result = yield* Fx.collectAll(fx);
|
|
90
|
+
assert.deepStrictEqual(result, [1, 2]);
|
|
91
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
92
|
+
|
|
93
|
+
it("skipEffect uses count from Effect", () =>
|
|
94
|
+
Effect.gen(function* () {
|
|
95
|
+
const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(Fx.skipEffect(Effect.succeed(2)));
|
|
96
|
+
const result = yield* Fx.collectAll(fx);
|
|
97
|
+
assert.deepStrictEqual(result, [3, 4]);
|
|
98
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
99
|
+
|
|
100
|
+
it("sliceEffect uses bounds from Effect", () =>
|
|
101
|
+
Effect.gen(function* () {
|
|
102
|
+
const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(
|
|
103
|
+
Fx.sliceEffect(Effect.succeed({ skip: 1, take: 2 })),
|
|
104
|
+
);
|
|
105
|
+
const result = yield* Fx.collectAll(fx);
|
|
106
|
+
assert.deepStrictEqual(result, [2, 3]);
|
|
107
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
108
|
+
|
|
109
|
+
it("takeUntilEffect stops when predicate effect succeeds with true", () =>
|
|
110
|
+
Effect.gen(function* () {
|
|
111
|
+
const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(
|
|
112
|
+
Fx.takeUntilEffect((n) => Effect.succeed(n > 2)),
|
|
113
|
+
);
|
|
114
|
+
const result = yield* Fx.collectAll(fx);
|
|
115
|
+
assert.deepStrictEqual(result, [1, 2]);
|
|
116
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
117
|
+
|
|
118
|
+
it("takeUntilEffect fails when predicate effect fails", () =>
|
|
119
|
+
Effect.gen(function* () {
|
|
120
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(
|
|
121
|
+
Fx.takeUntilEffect((n) => (n === 2 ? Effect.fail("boom") : Effect.succeed(false))),
|
|
122
|
+
);
|
|
123
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
124
|
+
assert(Exit.isFailure(exit));
|
|
125
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
126
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import * as Cause from "effect/Cause";
|
|
3
|
+
import { Data } from "effect";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Exit from "effect/Exit";
|
|
6
|
+
import * as Result from "effect/Result";
|
|
7
|
+
import { catchCauseIf, catchIf, catchTags } from "./Fx/combinators/catch.js";
|
|
8
|
+
import { Fx } from "./index.js";
|
|
9
|
+
|
|
10
|
+
describe("Fx.catchIf", () => {
|
|
11
|
+
it("recovers when predicate is true (typed failure)", () =>
|
|
12
|
+
Effect.gen(function* () {
|
|
13
|
+
const fx = Fx.fail(42).pipe(
|
|
14
|
+
catchIf(
|
|
15
|
+
(n: number) => n > 10,
|
|
16
|
+
(n) => Fx.succeed(`recovered:${n}`),
|
|
17
|
+
),
|
|
18
|
+
);
|
|
19
|
+
const result = yield* Fx.collectAll(fx);
|
|
20
|
+
assert.deepStrictEqual(result, ["recovered:42"]);
|
|
21
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
22
|
+
|
|
23
|
+
it("does not recover when predicate is false (re-fails)", () =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const fx = Fx.fail(3).pipe(
|
|
26
|
+
catchIf(
|
|
27
|
+
(n: number) => n > 10,
|
|
28
|
+
(n) => Fx.succeed(`recovered:${n}`),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
32
|
+
assert(Exit.isFailure(exit));
|
|
33
|
+
const failResult = Cause.findFail(exit.cause);
|
|
34
|
+
assert(failResult._tag === "Success");
|
|
35
|
+
assert.strictEqual(failResult.success.error, 3);
|
|
36
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
37
|
+
|
|
38
|
+
it("does not recover from defects (only Fail is considered)", () =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const fx = Fx.die("defect").pipe(
|
|
41
|
+
catchIf(
|
|
42
|
+
() => true,
|
|
43
|
+
() => Fx.succeed("nope"),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
47
|
+
assert(Exit.isFailure(exit));
|
|
48
|
+
assert(Cause.hasDies(exit.cause));
|
|
49
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
50
|
+
|
|
51
|
+
it("success path is unchanged", () =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(
|
|
54
|
+
catchIf(
|
|
55
|
+
(_: string) => true,
|
|
56
|
+
() => Fx.succeed(-1),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
const result = yield* Fx.collectAll(fx);
|
|
60
|
+
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
61
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
62
|
+
|
|
63
|
+
it("dual call style (data-first)", () =>
|
|
64
|
+
Effect.gen(function* () {
|
|
65
|
+
const fx = catchIf(
|
|
66
|
+
Fx.fail("err"),
|
|
67
|
+
(e: string) => e.length > 0,
|
|
68
|
+
(e) => Fx.succeed(e.toUpperCase()),
|
|
69
|
+
);
|
|
70
|
+
const result = yield* Fx.collectAll(fx);
|
|
71
|
+
assert.deepStrictEqual(result, ["ERR"]);
|
|
72
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Fx.catchCauseIf", () => {
|
|
76
|
+
it("recovers when predicate is true (e.g. has fails)", () =>
|
|
77
|
+
Effect.gen(function* () {
|
|
78
|
+
const fx = Fx.fail("boom").pipe(
|
|
79
|
+
catchCauseIf(Cause.hasFails, (cause) => {
|
|
80
|
+
const r = Cause.findFail(cause);
|
|
81
|
+
const msg = Result.isSuccess(r) ? r.success.error : "?";
|
|
82
|
+
return Fx.succeed(`recovered:${msg}`);
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
const result = yield* Fx.collectAll(fx);
|
|
86
|
+
assert.strictEqual(result[0], "recovered:boom");
|
|
87
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
88
|
+
|
|
89
|
+
it("recovers from defects when predicate matches cause", () =>
|
|
90
|
+
Effect.gen(function* () {
|
|
91
|
+
const fx = Fx.die("defect").pipe(catchCauseIf(Cause.hasDies, () => Fx.succeed("ok")));
|
|
92
|
+
const result = yield* Fx.collectAll(fx);
|
|
93
|
+
assert.deepStrictEqual(result, ["ok"]);
|
|
94
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
95
|
+
|
|
96
|
+
it("does not recover when predicate is false", () =>
|
|
97
|
+
Effect.gen(function* () {
|
|
98
|
+
const fx = Fx.die("oops").pipe(catchCauseIf(Cause.isEmpty, () => Fx.succeed("no")));
|
|
99
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
100
|
+
assert(Exit.isFailure(exit));
|
|
101
|
+
assert(Cause.hasDies(exit.cause));
|
|
102
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
103
|
+
|
|
104
|
+
it("success path is unchanged", () =>
|
|
105
|
+
Effect.gen(function* () {
|
|
106
|
+
const fx = Fx.fromIterable([1, 2]).pipe(
|
|
107
|
+
catchCauseIf(
|
|
108
|
+
() => true,
|
|
109
|
+
() => Fx.succeed(0),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
const result = yield* Fx.collectAll(fx);
|
|
113
|
+
assert.deepStrictEqual(result, [1, 2]);
|
|
114
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
115
|
+
|
|
116
|
+
it("dual call style (data-first)", () =>
|
|
117
|
+
Effect.gen(function* () {
|
|
118
|
+
const fx = catchCauseIf(Fx.fail("x"), Cause.hasFails, (cause) =>
|
|
119
|
+
Fx.succeed(Result.isSuccess(Cause.findFail(cause)) ? "handled" : "unhandled"),
|
|
120
|
+
);
|
|
121
|
+
const result = yield* Fx.collectAll(fx);
|
|
122
|
+
assert.strictEqual(result[0], "handled");
|
|
123
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("Fx.catchTags", () => {
|
|
127
|
+
class ErrorA extends Data.TaggedError("A")<{ readonly n: number }> {}
|
|
128
|
+
class ErrorB extends Data.TaggedError("B")<{ readonly msg: string }> {}
|
|
129
|
+
|
|
130
|
+
it("recovers from multiple tagged failures with one call", () =>
|
|
131
|
+
Effect.gen(function* () {
|
|
132
|
+
const fx = Fx.fail(new ErrorA({ n: 1 })).pipe(
|
|
133
|
+
catchTags({
|
|
134
|
+
A: (e) => Fx.succeed(`A:${e.n}`),
|
|
135
|
+
B: (e) => Fx.succeed(`B:${e.msg}`),
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
const result = yield* Fx.collectAll(fx);
|
|
139
|
+
assert.deepStrictEqual(result, ["A:1"]);
|
|
140
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
141
|
+
|
|
142
|
+
it("matches second tag when first fails with B", () =>
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const fx = Fx.fail(new ErrorB({ msg: "hello" })).pipe(
|
|
145
|
+
catchTags({
|
|
146
|
+
A: (e) => Fx.succeed(`A:${e.n}`),
|
|
147
|
+
B: (e) => Fx.succeed(`B:${e.msg}`),
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
const result = yield* Fx.collectAll(fx);
|
|
151
|
+
assert.deepStrictEqual(result, ["B:hello"]);
|
|
152
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
153
|
+
|
|
154
|
+
it("does not catch untagged or other tags (re-fails)", () =>
|
|
155
|
+
Effect.gen(function* () {
|
|
156
|
+
const fx = Fx.fail("plain").pipe(
|
|
157
|
+
catchTags({
|
|
158
|
+
A: () => Fx.succeed(1),
|
|
159
|
+
B: () => Fx.succeed(2),
|
|
160
|
+
} as {
|
|
161
|
+
A: (e: ErrorA) => Fx<number, never, never>;
|
|
162
|
+
B: (e: ErrorB) => Fx<number, never, never>;
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
166
|
+
assert(Exit.isFailure(exit));
|
|
167
|
+
const failResult = Cause.findFail(exit.cause);
|
|
168
|
+
assert(failResult._tag === "Success");
|
|
169
|
+
assert.strictEqual(failResult.success.error, "plain");
|
|
170
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
171
|
+
|
|
172
|
+
it("does not recover from defects (only Fail with _tag)", () =>
|
|
173
|
+
Effect.gen(function* () {
|
|
174
|
+
const fx = Fx.die("defect").pipe(
|
|
175
|
+
catchTags({
|
|
176
|
+
A: () => Fx.succeed(1),
|
|
177
|
+
B: () => Fx.succeed(2),
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
181
|
+
assert(Exit.isFailure(exit));
|
|
182
|
+
assert(Cause.hasDies(exit.cause));
|
|
183
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
184
|
+
|
|
185
|
+
it("success path is unchanged", () =>
|
|
186
|
+
Effect.gen(function* () {
|
|
187
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(
|
|
188
|
+
catchTags({
|
|
189
|
+
A: () => Fx.succeed(-1),
|
|
190
|
+
B: () => Fx.succeed(-2),
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
const result = yield* Fx.collectAll(fx);
|
|
194
|
+
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
195
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
196
|
+
|
|
197
|
+
it("dual call style (data-first)", () =>
|
|
198
|
+
Effect.gen(function* () {
|
|
199
|
+
const fx = catchTags(Fx.fail(new ErrorA({ n: 42 })), {
|
|
200
|
+
A: (e) => Fx.succeed(e.n * 2),
|
|
201
|
+
B: () => Fx.succeed(0),
|
|
202
|
+
});
|
|
203
|
+
const result = yield* Fx.collectAll(fx);
|
|
204
|
+
assert.deepStrictEqual(result, [84]);
|
|
205
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
206
|
+
});
|
package/src/Fx.catch.test.ts
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Exit from "effect/Exit";
|
|
4
|
+
import { Fx } from "./index.js";
|
|
5
|
+
|
|
6
|
+
describe("Fx dropUntil / dropUntilEffect combinators", () => {
|
|
7
|
+
it("dropUntil drops until predicate is true and includes first match", () =>
|
|
8
|
+
Effect.gen(function* () {
|
|
9
|
+
const fx = Fx.fromIterable([1, 2, 3, 4, 5]).pipe(Fx.dropUntil((n) => n >= 4));
|
|
10
|
+
const result = yield* Fx.collectAll(fx);
|
|
11
|
+
assert.deepStrictEqual(result, [4, 5]);
|
|
12
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
13
|
+
|
|
14
|
+
it("dropUntil emits all when predicate true on first element", () =>
|
|
15
|
+
Effect.gen(function* () {
|
|
16
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.dropUntil(() => true));
|
|
17
|
+
const result = yield* Fx.collectAll(fx);
|
|
18
|
+
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
19
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
20
|
+
|
|
21
|
+
it("dropUntil emits none when predicate never true", () =>
|
|
22
|
+
Effect.gen(function* () {
|
|
23
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.dropUntil(() => false));
|
|
24
|
+
const result = yield* Fx.collectAll(fx);
|
|
25
|
+
assert.deepStrictEqual(result, []);
|
|
26
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
27
|
+
|
|
28
|
+
it("dropUntil includes first matching element only once", () =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const fx = Fx.fromIterable([1, 2, 3, 4, 5]).pipe(Fx.dropUntil((n) => n === 3));
|
|
31
|
+
const result = yield* Fx.collectAll(fx);
|
|
32
|
+
assert.deepStrictEqual(result, [3, 4, 5]);
|
|
33
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
34
|
+
|
|
35
|
+
it("dropUntilEffect drops until effectful predicate is true", () =>
|
|
36
|
+
Effect.gen(function* () {
|
|
37
|
+
const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(
|
|
38
|
+
Fx.dropUntilEffect((n) => Effect.succeed(n >= 3)),
|
|
39
|
+
);
|
|
40
|
+
const result = yield* Fx.collectAll(fx);
|
|
41
|
+
assert.deepStrictEqual(result, [3, 4]);
|
|
42
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
43
|
+
|
|
44
|
+
it("dropUntilEffect fails when predicate effect fails", () =>
|
|
45
|
+
Effect.gen(function* () {
|
|
46
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(
|
|
47
|
+
Fx.dropUntilEffect((n) => (n === 2 ? Effect.fail("err") : Effect.succeed(false))),
|
|
48
|
+
);
|
|
49
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
50
|
+
assert(Exit.isFailure(exit));
|
|
51
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
52
|
+
|
|
53
|
+
it("dropUntilEffect emits from first element when predicate effect succeeds true immediately", () =>
|
|
54
|
+
Effect.gen(function* () {
|
|
55
|
+
const fx = Fx.fromIterable([10, 20, 30]).pipe(
|
|
56
|
+
Fx.dropUntilEffect((n) => Effect.succeed(n > 0)),
|
|
57
|
+
);
|
|
58
|
+
const result = yield* Fx.collectAll(fx);
|
|
59
|
+
assert.deepStrictEqual(result, [10, 20, 30]);
|
|
60
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
61
|
+
});
|
package/src/Fx.lifecycle.test.ts
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import * as Cause from "effect/Cause";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Exit from "effect/Exit";
|
|
5
|
+
import { Fx } from "./index.js";
|
|
6
|
+
|
|
7
|
+
describe("Fx.mapError", () => {
|
|
8
|
+
it("maps typed failure to new error type", () =>
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const fx = Fx.fail("err").pipe(Fx.mapError((s) => ({ msg: s })));
|
|
11
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
12
|
+
assert(Exit.isFailure(exit));
|
|
13
|
+
const result = Cause.findFail(exit.cause);
|
|
14
|
+
assert(result._tag === "Success");
|
|
15
|
+
assert.deepStrictEqual(result.success.error, { msg: "err" });
|
|
16
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
17
|
+
|
|
18
|
+
it("dual call style: data-last", () =>
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const fx = Fx.mapError(Fx.fail(42), (n) => `n=${n}`);
|
|
21
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
22
|
+
assert(Exit.isFailure(exit));
|
|
23
|
+
const result = Cause.findFail(exit.cause);
|
|
24
|
+
assert(result._tag === "Success");
|
|
25
|
+
assert.strictEqual(result.success.error, "n=42");
|
|
26
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
27
|
+
|
|
28
|
+
it("dual call style: data-first pipe", () =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const fx = Fx.fromIterable([1, 2]).pipe(Fx.mapError((e: string) => e.toUpperCase()));
|
|
31
|
+
const result = yield* Fx.collectAll(fx);
|
|
32
|
+
assert.deepStrictEqual(result, [1, 2]);
|
|
33
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
34
|
+
|
|
35
|
+
it("preserves success values", () =>
|
|
36
|
+
Effect.gen(function* () {
|
|
37
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.mapError((e: string) => e));
|
|
38
|
+
const result = yield* Fx.collectAll(fx);
|
|
39
|
+
assert.deepStrictEqual(result, [1, 2, 3]);
|
|
40
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
41
|
+
|
|
42
|
+
it("defects are not mapped (Cause.map only maps Fail)", () =>
|
|
43
|
+
Effect.gen(function* () {
|
|
44
|
+
const fx = Fx.die("defect").pipe(Fx.mapError((_e: string) => "mapped"));
|
|
45
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
46
|
+
assert(Exit.isFailure(exit));
|
|
47
|
+
assert(Cause.hasDies(exit.cause));
|
|
48
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("Fx.mapBoth", () => {
|
|
52
|
+
it("maps both success and failure channels", () =>
|
|
53
|
+
Effect.gen(function* () {
|
|
54
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(
|
|
55
|
+
Fx.mapBoth({
|
|
56
|
+
onSuccess: (n) => n * 10,
|
|
57
|
+
onFailure: (e: string) => e.length,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
const result = yield* Fx.collectAll(fx);
|
|
61
|
+
assert.deepStrictEqual(result, [10, 20, 30]);
|
|
62
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
63
|
+
|
|
64
|
+
it("maps failure when stream fails", () =>
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const fx = Fx.fail("boom").pipe(
|
|
67
|
+
Fx.mapBoth({
|
|
68
|
+
onSuccess: (a: number) => a,
|
|
69
|
+
onFailure: (s: string) => ({ message: s }),
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
73
|
+
assert(Exit.isFailure(exit));
|
|
74
|
+
const result = Cause.findFail(exit.cause);
|
|
75
|
+
assert(result._tag === "Success");
|
|
76
|
+
assert.deepStrictEqual(result.success.error, { message: "boom" });
|
|
77
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
78
|
+
|
|
79
|
+
it("dual call style: data-last", () =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const fx = Fx.mapBoth(Fx.fromIterable(["a", "b"]), {
|
|
82
|
+
onSuccess: (s) => s.toUpperCase(),
|
|
83
|
+
onFailure: (e: number) => String(e),
|
|
84
|
+
});
|
|
85
|
+
const result = yield* Fx.collectAll(fx);
|
|
86
|
+
assert.deepStrictEqual(result, ["A", "B"]);
|
|
87
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
88
|
+
|
|
89
|
+
it("preserves defects (onFailure only maps Fail)", () =>
|
|
90
|
+
Effect.gen(function* () {
|
|
91
|
+
const fx = Fx.die("oops").pipe(
|
|
92
|
+
Fx.mapBoth({
|
|
93
|
+
onSuccess: (a: number) => a,
|
|
94
|
+
onFailure: (_e: string) => "mapped",
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
const exit = yield* Effect.exit(Fx.collectAll(fx));
|
|
98
|
+
assert(Exit.isFailure(exit));
|
|
99
|
+
assert(Cause.hasDies(exit.cause));
|
|
100
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
101
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import { Fx } from "./index.js";
|
|
4
|
+
import * as ServiceMap from "effect/ServiceMap";
|
|
5
|
+
|
|
6
|
+
const Foo = ServiceMap.Service<{ readonly n: number }>("Test/Foo");
|
|
7
|
+
|
|
8
|
+
describe("Fx provideService", () => {
|
|
9
|
+
it("provides a single service and Fx can use it", () =>
|
|
10
|
+
Effect.gen(function* () {
|
|
11
|
+
const fx = Fx.fromIterable([1, 2, 3]).pipe(
|
|
12
|
+
Fx.mapEffect((x) =>
|
|
13
|
+
Effect.gen(function* () {
|
|
14
|
+
const foo = yield* Foo;
|
|
15
|
+
return x + foo.n;
|
|
16
|
+
}),
|
|
17
|
+
),
|
|
18
|
+
Fx.provideService(Foo, { n: 10 }),
|
|
19
|
+
);
|
|
20
|
+
const result = yield* Fx.collectAll(fx);
|
|
21
|
+
assert.deepStrictEqual(result, [11, 12, 13]);
|
|
22
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
23
|
+
|
|
24
|
+
it("provideService(tag, service) curried form", () =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const withFoo = Fx.provideService(Foo, { n: 1 });
|
|
27
|
+
const fx = Fx.fromIterable([0]).pipe(
|
|
28
|
+
Fx.mapEffect((x) =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const f = yield* Foo;
|
|
31
|
+
return x + f.n;
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
withFoo,
|
|
35
|
+
);
|
|
36
|
+
const result = yield* Fx.collectAll(fx);
|
|
37
|
+
assert.deepStrictEqual(result, [1]);
|
|
38
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("Fx provideServiceEffect", () => {
|
|
42
|
+
it("provides a service from an effect and Fx can use it", () =>
|
|
43
|
+
Effect.gen(function* () {
|
|
44
|
+
const fx = Fx.fromIterable([1, 2]).pipe(
|
|
45
|
+
Fx.mapEffect((x) =>
|
|
46
|
+
Effect.gen(function* () {
|
|
47
|
+
const foo = yield* Foo;
|
|
48
|
+
return x + foo.n;
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
Fx.provideServiceEffect(Foo, Effect.succeed({ n: 100 })),
|
|
52
|
+
);
|
|
53
|
+
const result = yield* Fx.collectAll(fx);
|
|
54
|
+
assert.deepStrictEqual(result, [101, 102]);
|
|
55
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
56
|
+
|
|
57
|
+
it("provideServiceEffect runs the effect when the stream runs (service effect can have requirements)", () =>
|
|
58
|
+
Effect.gen(function* () {
|
|
59
|
+
let built = false;
|
|
60
|
+
const makeFoo = Effect.sync(() => {
|
|
61
|
+
built = true;
|
|
62
|
+
return { n: 7 };
|
|
63
|
+
});
|
|
64
|
+
const fx = Fx.fromIterable([0]).pipe(
|
|
65
|
+
Fx.mapEffect((x) =>
|
|
66
|
+
Effect.gen(function* () {
|
|
67
|
+
const f = yield* Foo;
|
|
68
|
+
return x + f.n;
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
Fx.provideServiceEffect(Foo, makeFoo),
|
|
72
|
+
);
|
|
73
|
+
assert.strictEqual(built, false);
|
|
74
|
+
const result = yield* Fx.collectAll(fx);
|
|
75
|
+
assert.strictEqual(built, true);
|
|
76
|
+
assert.deepStrictEqual(result, [7]);
|
|
77
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
78
|
+
|
|
79
|
+
it("provideServiceEffect(tag, effect) curried form", () =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const withFooFromEffect = Fx.provideServiceEffect(Foo, Effect.succeed({ n: 5 }));
|
|
82
|
+
const fx = Fx.fromIterable([0]).pipe(
|
|
83
|
+
Fx.mapEffect((x) =>
|
|
84
|
+
Effect.gen(function* () {
|
|
85
|
+
const f = yield* Foo;
|
|
86
|
+
return x + f.n;
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
withFooFromEffect,
|
|
90
|
+
);
|
|
91
|
+
const result = yield* Fx.collectAll(fx);
|
|
92
|
+
assert.deepStrictEqual(result, [5]);
|
|
93
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
94
|
+
});
|