@typed/fx 2.0.0-beta.0 → 2.0.0-beta.1

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.
Files changed (119) hide show
  1. package/README.md +24 -1
  2. package/dist/Fx/combinators/additive.d.ts +94 -0
  3. package/dist/Fx/combinators/additive.d.ts.map +1 -0
  4. package/dist/Fx/combinators/additive.js +92 -0
  5. package/dist/Fx/combinators/catch.d.ts +61 -0
  6. package/dist/Fx/combinators/catch.d.ts.map +1 -1
  7. package/dist/Fx/combinators/catch.js +54 -0
  8. package/dist/Fx/combinators/changesWithEffect.d.ts +20 -0
  9. package/dist/Fx/combinators/changesWithEffect.d.ts.map +1 -0
  10. package/dist/Fx/combinators/changesWithEffect.js +28 -0
  11. package/dist/Fx/combinators/dropUntil.d.ts +29 -0
  12. package/dist/Fx/combinators/dropUntil.d.ts.map +1 -0
  13. package/dist/Fx/combinators/dropUntil.js +23 -0
  14. package/dist/Fx/combinators/flatMapConcurrently.d.ts.map +1 -1
  15. package/dist/Fx/combinators/flatMapConcurrently.js +3 -2
  16. package/dist/Fx/combinators/index.d.ts +10 -0
  17. package/dist/Fx/combinators/index.d.ts.map +1 -1
  18. package/dist/Fx/combinators/index.js +10 -0
  19. package/dist/Fx/combinators/keyed.d.ts +1 -1
  20. package/dist/Fx/combinators/keyed.d.ts.map +1 -1
  21. package/dist/Fx/combinators/mapBoth.d.ts +21 -0
  22. package/dist/Fx/combinators/mapBoth.d.ts.map +1 -0
  23. package/dist/Fx/combinators/mapBoth.js +14 -0
  24. package/dist/Fx/combinators/mapError.d.ts +17 -0
  25. package/dist/Fx/combinators/mapError.d.ts.map +1 -0
  26. package/dist/Fx/combinators/mapError.js +16 -0
  27. package/dist/Fx/combinators/provide.d.ts +34 -1
  28. package/dist/Fx/combinators/provide.d.ts.map +1 -1
  29. package/dist/Fx/combinators/provide.js +27 -0
  30. package/dist/Fx/combinators/result.d.ts +23 -0
  31. package/dist/Fx/combinators/result.d.ts.map +1 -0
  32. package/dist/Fx/combinators/result.js +32 -0
  33. package/dist/Fx/combinators/scan.d.ts +33 -0
  34. package/dist/Fx/combinators/scan.d.ts.map +1 -0
  35. package/dist/Fx/combinators/scan.js +38 -0
  36. package/dist/Fx/combinators/skip.d.ts +13 -0
  37. package/dist/Fx/combinators/skip.d.ts.map +1 -1
  38. package/dist/Fx/combinators/skip.js +11 -0
  39. package/dist/Fx/combinators/skipWhile.d.ts +49 -0
  40. package/dist/Fx/combinators/skipWhile.d.ts.map +1 -0
  41. package/dist/Fx/combinators/skipWhile.js +66 -0
  42. package/dist/Fx/combinators/slice.d.ts +13 -0
  43. package/dist/Fx/combinators/slice.d.ts.map +1 -1
  44. package/dist/Fx/combinators/slice.js +11 -0
  45. package/dist/Fx/combinators/take.d.ts +13 -0
  46. package/dist/Fx/combinators/take.d.ts.map +1 -1
  47. package/dist/Fx/combinators/take.js +11 -0
  48. package/dist/Fx/combinators/takeUntil.d.ts +14 -0
  49. package/dist/Fx/combinators/takeUntil.d.ts.map +1 -1
  50. package/dist/Fx/combinators/takeUntil.js +14 -0
  51. package/dist/Fx/combinators/takeWhile.d.ts +29 -0
  52. package/dist/Fx/combinators/takeWhile.d.ts.map +1 -0
  53. package/dist/Fx/combinators/takeWhile.js +23 -0
  54. package/dist/Fx/combinators/zip.d.ts +75 -0
  55. package/dist/Fx/combinators/zip.d.ts.map +1 -0
  56. package/dist/Fx/combinators/zip.js +100 -0
  57. package/dist/Fx/constructors/at.d.ts +2 -2
  58. package/dist/Fx/constructors/at.d.ts.map +1 -1
  59. package/dist/Fx/constructors/periodic.d.ts +1 -1
  60. package/dist/Fx/constructors/periodic.d.ts.map +1 -1
  61. package/dist/Push/Push.d.ts +64 -1
  62. package/dist/Push/Push.d.ts.map +1 -1
  63. package/dist/Push/Push.js +57 -0
  64. package/dist/RefSubject/RefDateTime.d.ts +4 -4
  65. package/dist/RefSubject/RefDateTime.d.ts.map +1 -1
  66. package/dist/RefSubject/RefSubject.d.ts +47 -0
  67. package/dist/RefSubject/RefSubject.d.ts.map +1 -1
  68. package/dist/RefSubject/RefSubject.js +80 -1
  69. package/dist/Sink/combinators.d.ts +57 -0
  70. package/dist/Sink/combinators.d.ts.map +1 -1
  71. package/dist/Sink/combinators.js +104 -1
  72. package/dist/Versioned/Versioned.d.ts +30 -0
  73. package/dist/Versioned/Versioned.d.ts.map +1 -1
  74. package/dist/Versioned/Versioned.js +18 -0
  75. package/package.json +16 -12
  76. package/src/Fx/combinators/additive.ts +142 -0
  77. package/src/Fx/combinators/catch.ts +256 -0
  78. package/src/Fx/combinators/changesWithEffect.ts +66 -0
  79. package/src/Fx/combinators/dropUntil.ts +47 -0
  80. package/src/Fx/combinators/flatMapConcurrently.ts +5 -2
  81. package/src/Fx/combinators/index.ts +10 -0
  82. package/src/Fx/combinators/keyed.ts +2 -2
  83. package/src/Fx/combinators/mapBoth.ts +40 -0
  84. package/src/Fx/combinators/mapError.ts +28 -0
  85. package/src/Fx/combinators/provide.ts +63 -1
  86. package/src/Fx/combinators/result.ts +39 -0
  87. package/src/Fx/combinators/scan.ts +82 -0
  88. package/src/Fx/combinators/skip.ts +21 -0
  89. package/src/Fx/combinators/skipWhile.ts +100 -0
  90. package/src/Fx/combinators/slice.ts +23 -0
  91. package/src/Fx/combinators/take.ts +21 -0
  92. package/src/Fx/combinators/takeUntil.ts +38 -0
  93. package/src/Fx/combinators/takeWhile.ts +47 -0
  94. package/src/Fx/combinators/zip.ts +175 -0
  95. package/src/Fx/constructors/at.ts +3 -3
  96. package/src/Fx/constructors/periodic.ts +1 -1
  97. package/src/Fx.additive-combinators.test.ts +126 -0
  98. package/src/Fx.catch-additive.test.ts +206 -0
  99. package/src/Fx.catch.test.ts +1 -2
  100. package/src/Fx.dropUntil.test.ts +61 -0
  101. package/src/Fx.lifecycle.test.ts +1 -2
  102. package/src/Fx.mapError-mapBoth.test.ts +101 -0
  103. package/src/Fx.provide-combinators.test.ts +94 -0
  104. package/src/Fx.result-changesWithEffect.test.ts +112 -0
  105. package/src/Fx.scan.test.ts +73 -0
  106. package/src/Fx.takeWhile-skipWhile.test.ts +84 -0
  107. package/src/Fx.zip-merge-additive.test.ts +171 -0
  108. package/src/Fx.zip.test.ts +133 -0
  109. package/src/Push/Push.ts +170 -1
  110. package/src/Push.additive.test.ts +256 -0
  111. package/src/RefSubject/RefDateTime.ts +6 -6
  112. package/src/RefSubject/RefSubject.ts +104 -3
  113. package/src/RefSubject.additive-parity.test.ts +101 -0
  114. package/src/Sink/combinators.ts +123 -1
  115. package/src/Sink.combinators.test.ts +88 -0
  116. package/src/Sink.reduce-collect-head-last.test.ts +107 -0
  117. package/src/Versioned/Versioned.ts +76 -0
  118. package/src/Versioned.filterMap.test.ts +91 -0
  119. package/tsconfig.json +0 -6
@@ -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
+ });
@@ -0,0 +1,112 @@
1
+ import { assert, describe, it } from "vitest";
2
+ import * as Cause from "effect/Cause";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Result from "effect/Result";
5
+ import { changesWithEffect } from "./Fx/combinators/changesWithEffect.js";
6
+ import { result } from "./Fx/combinators/result.js";
7
+ import { Fx } from "./index.js";
8
+
9
+ describe("Fx.result", () => {
10
+ it("materializes success values as Result.succeed", () =>
11
+ Effect.gen(function* () {
12
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(result);
13
+ const collected = yield* Fx.collectAll(fx);
14
+ assert.strictEqual(collected.length, 3);
15
+ assert(Result.isSuccess(collected[0]!) && collected[0].success === 1);
16
+ assert(Result.isSuccess(collected[1]!) && collected[1].success === 2);
17
+ assert(Result.isSuccess(collected[2]!) && collected[2].success === 3);
18
+ }).pipe(Effect.scoped, Effect.runPromise));
19
+
20
+ it("materializes typed failure as Result.fail(Cause)", () =>
21
+ Effect.gen(function* () {
22
+ const fx = Fx.fail("err").pipe(result);
23
+ const collected = yield* Fx.collectAll(fx);
24
+ assert.strictEqual(collected.length, 1);
25
+ assert(Result.isFailure(collected[0]!));
26
+ const cause = collected[0].failure;
27
+ const failResult = Cause.findFail(cause);
28
+ assert(failResult._tag === "Success");
29
+ assert.strictEqual(failResult.success.error, "err");
30
+ }).pipe(Effect.scoped, Effect.runPromise));
31
+
32
+ it("materializes defect as Result.fail(Cause) with die", () =>
33
+ Effect.gen(function* () {
34
+ const fx = Fx.die("defect").pipe(result);
35
+ const collected = yield* Fx.collectAll(fx);
36
+ assert.strictEqual(collected.length, 1);
37
+ assert(Result.isFailure(collected[0]!));
38
+ assert(Cause.hasDies(collected[0].failure));
39
+ }).pipe(Effect.scoped, Effect.runPromise));
40
+
41
+ it("materializes interrupt as Result.fail(Cause) with interrupt", () =>
42
+ Effect.gen(function* () {
43
+ const fx = result(Fx.interrupt());
44
+ const collected = yield* Fx.collectAll(fx);
45
+ assert.strictEqual(collected.length, 1);
46
+ assert(Result.isFailure(collected[0]!));
47
+ assert(Cause.hasInterrupts(collected[0].failure));
48
+ }).pipe(Effect.scoped, Effect.runPromise));
49
+
50
+ it("emits both successes and failures in order", () =>
51
+ Effect.gen(function* () {
52
+ const fx = Fx.fromIterable([1, 2])
53
+ .pipe(Fx.continueWith(() => Fx.fail("e")))
54
+ .pipe(Fx.continueWith(() => Fx.fromIterable([3])))
55
+ .pipe(result);
56
+ const collected = yield* Fx.collectAll(fx);
57
+ assert.strictEqual(collected.length, 4);
58
+ assert(Result.isSuccess(collected[0]!) && collected[0].success === 1);
59
+ assert(Result.isSuccess(collected[1]!) && collected[1].success === 2);
60
+ assert(Result.isFailure(collected[2]!));
61
+ assert(Result.isSuccess(collected[3]!) && collected[3].success === 3);
62
+ }).pipe(Effect.scoped, Effect.runPromise));
63
+ });
64
+
65
+ describe("Fx.changesWithEffect", () => {
66
+ it("emits first value and then only when effect returns false (changed)", () =>
67
+ Effect.gen(function* () {
68
+ const fx = Fx.fromIterable([1, 2, 2, 3, 3, 3]).pipe(
69
+ changesWithEffect((prev, next) => Effect.succeed(prev === next)),
70
+ );
71
+ const out = yield* Fx.collectAll(fx);
72
+ assert.deepStrictEqual(out, [1, 2, 3]);
73
+ }).pipe(Effect.scoped, Effect.runPromise));
74
+
75
+ it("emits all when effect always returns false", () =>
76
+ Effect.gen(function* () {
77
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(changesWithEffect(() => Effect.succeed(false)));
78
+ const out = yield* Fx.collectAll(fx);
79
+ assert.deepStrictEqual(out, [1, 2, 3]);
80
+ }).pipe(Effect.scoped, Effect.runPromise));
81
+
82
+ it("emits only first when effect always returns true after first", () =>
83
+ Effect.gen(function* () {
84
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(
85
+ changesWithEffect((_prev, next) => Effect.succeed(next > 1)),
86
+ );
87
+ const out = yield* Fx.collectAll(fx);
88
+ assert.deepStrictEqual(out, [1]);
89
+ }).pipe(Effect.scoped, Effect.runPromise));
90
+
91
+ it("propagates failure when effect fails", () =>
92
+ Effect.gen(function* () {
93
+ const fx = Fx.fromIterable([1, 2]).pipe(
94
+ changesWithEffect((_prev, next) =>
95
+ next === 2 ? Effect.fail("err" as const) : Effect.succeed(false),
96
+ ),
97
+ );
98
+ const exit = yield* Effect.exit(Fx.collectAll(fx));
99
+ assert(exit._tag === "Failure");
100
+ const fail = Cause.findFail(exit.cause);
101
+ assert(fail._tag === "Success" && fail.success.error === "err");
102
+ }).pipe(Effect.scoped, Effect.runPromise));
103
+
104
+ it("dual call style: data-first", () =>
105
+ Effect.gen(function* () {
106
+ const fx = changesWithEffect(Fx.fromIterable([1, 1, 2, 2]), (a, b) =>
107
+ Effect.succeed(a === b),
108
+ );
109
+ const out = yield* Fx.collectAll(fx);
110
+ assert.deepStrictEqual(out, [1, 2]);
111
+ }).pipe(Effect.scoped, Effect.runPromise));
112
+ });
@@ -0,0 +1,73 @@
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
+ import { scan, scanEffect } from "./Fx/combinators/scan.js";
6
+
7
+ describe("Fx scan / scanEffect combinators", () => {
8
+ describe("scan", () => {
9
+ it("emits initial then accumulated state (sum)", () =>
10
+ Effect.gen(function* () {
11
+ const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(scan(0, (s, a) => s + a));
12
+ const result = yield* Fx.collectAll(fx);
13
+ assert.deepStrictEqual(result, [0, 1, 3, 6, 10]);
14
+ }).pipe(Effect.scoped, Effect.runPromise));
15
+
16
+ it("emits initial only when source is empty", () =>
17
+ Effect.gen(function* () {
18
+ const fx = Fx.fromIterable<number>([]).pipe(scan(42, (s, a) => s + a));
19
+ const result = yield* Fx.collectAll(fx);
20
+ assert.deepStrictEqual(result, [42]);
21
+ }).pipe(Effect.scoped, Effect.runPromise));
22
+
23
+ it("data-last form: pipe(fx, scan(initial, f))", () =>
24
+ Effect.gen(function* () {
25
+ const fx = Fx.fromIterable(["a", "b", "c"]).pipe(scan("", (acc, s) => acc + s));
26
+ const result = yield* Fx.collectAll(fx);
27
+ assert.deepStrictEqual(result, ["", "a", "ab", "abc"]);
28
+ }).pipe(Effect.scoped, Effect.runPromise));
29
+
30
+ it("data-first form: scan(fx, initial, f)", () =>
31
+ Effect.gen(function* () {
32
+ const fx = scan(Fx.fromIterable([2, 3, 4]), 1, (s, a) => s * a);
33
+ const result = yield* Fx.collectAll(fx);
34
+ assert.deepStrictEqual(result, [1, 2, 6, 24]);
35
+ }).pipe(Effect.scoped, Effect.runPromise));
36
+ });
37
+
38
+ describe("scanEffect", () => {
39
+ it("emits initial then effectfully accumulated state", () =>
40
+ Effect.gen(function* () {
41
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(scanEffect(0, (s, a) => Effect.succeed(s + a)));
42
+ const result = yield* Fx.collectAll(fx);
43
+ assert.deepStrictEqual(result, [0, 1, 3, 6]);
44
+ }).pipe(Effect.scoped, Effect.runPromise));
45
+
46
+ it("emits initial only when source is empty", () =>
47
+ Effect.gen(function* () {
48
+ const fx = Fx.fromIterable<number>([]).pipe(
49
+ scanEffect(10, (s, a) => Effect.succeed(s + a)),
50
+ );
51
+ const result = yield* Fx.collectAll(fx);
52
+ assert.deepStrictEqual(result, [10]);
53
+ }).pipe(Effect.scoped, Effect.runPromise));
54
+
55
+ it("fails when reducer effect fails", () =>
56
+ Effect.gen(function* () {
57
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(
58
+ scanEffect(0, (s, a) => (a === 2 ? Effect.fail("boom") : Effect.succeed(s + a))),
59
+ );
60
+ const exit = yield* Effect.exit(Fx.collectAll(fx));
61
+ assert(Exit.isFailure(exit));
62
+ }).pipe(Effect.scoped, Effect.runPromise));
63
+
64
+ it("data-first form: scanEffect(fx, initial, f)", () =>
65
+ Effect.gen(function* () {
66
+ const fx = scanEffect(Fx.fromIterable([1, 2, 3]), [] as number[], (acc, n) =>
67
+ Effect.succeed([...acc, n]),
68
+ );
69
+ const result = yield* Fx.collectAll(fx);
70
+ assert.deepStrictEqual(result, [[], [1], [1, 2], [1, 2, 3]]);
71
+ }).pipe(Effect.scoped, Effect.runPromise));
72
+ });
73
+ });
@@ -0,0 +1,84 @@
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 takeWhile / skipWhile combinators", () => {
7
+ it("takeWhile emits while predicate is true and excludes first false", () =>
8
+ Effect.gen(function* () {
9
+ const fx = Fx.fromIterable([1, 2, 3, 4, 5]).pipe(Fx.takeWhile((n) => n < 4));
10
+ const result = yield* Fx.collectAll(fx);
11
+ assert.deepStrictEqual(result, [1, 2, 3]);
12
+ }).pipe(Effect.scoped, Effect.runPromise));
13
+
14
+ it("takeWhile emits all when predicate always true", () =>
15
+ Effect.gen(function* () {
16
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.takeWhile(() => true));
17
+ const result = yield* Fx.collectAll(fx);
18
+ assert.deepStrictEqual(result, [1, 2, 3]);
19
+ }).pipe(Effect.scoped, Effect.runPromise));
20
+
21
+ it("takeWhile emits none when predicate false on first", () =>
22
+ Effect.gen(function* () {
23
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.takeWhile((n) => n > 1));
24
+ const result = yield* Fx.collectAll(fx);
25
+ assert.deepStrictEqual(result, []);
26
+ }).pipe(Effect.scoped, Effect.runPromise));
27
+
28
+ it("takeWhileEffect emits while effectful predicate is true", () =>
29
+ Effect.gen(function* () {
30
+ const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(
31
+ Fx.takeWhileEffect((n) => Effect.succeed(n < 3)),
32
+ );
33
+ const result = yield* Fx.collectAll(fx);
34
+ assert.deepStrictEqual(result, [1, 2]);
35
+ }).pipe(Effect.scoped, Effect.runPromise));
36
+
37
+ it("takeWhileEffect fails when predicate effect fails", () =>
38
+ Effect.gen(function* () {
39
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(
40
+ Fx.takeWhileEffect((n) => (n === 2 ? Effect.fail("err") : Effect.succeed(true))),
41
+ );
42
+ const exit = yield* Effect.exit(Fx.collectAll(fx));
43
+ assert(Exit.isFailure(exit));
44
+ }).pipe(Effect.scoped, Effect.runPromise));
45
+
46
+ it("skipWhile skips while predicate true and emits from first false", () =>
47
+ Effect.gen(function* () {
48
+ const fx = Fx.fromIterable([1, 2, 3, 4, 5]).pipe(Fx.skipWhile((n) => n < 3));
49
+ const result = yield* Fx.collectAll(fx);
50
+ assert.deepStrictEqual(result, [3, 4, 5]);
51
+ }).pipe(Effect.scoped, Effect.runPromise));
52
+
53
+ it("skipWhile emits all when predicate always false", () =>
54
+ Effect.gen(function* () {
55
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.skipWhile(() => false));
56
+ const result = yield* Fx.collectAll(fx);
57
+ assert.deepStrictEqual(result, [1, 2, 3]);
58
+ }).pipe(Effect.scoped, Effect.runPromise));
59
+
60
+ it("skipWhile emits none when predicate always true", () =>
61
+ Effect.gen(function* () {
62
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(Fx.skipWhile(() => true));
63
+ const result = yield* Fx.collectAll(fx);
64
+ assert.deepStrictEqual(result, []);
65
+ }).pipe(Effect.scoped, Effect.runPromise));
66
+
67
+ it("skipWhileEffect skips while effectful predicate is true", () =>
68
+ Effect.gen(function* () {
69
+ const fx = Fx.fromIterable([1, 2, 3, 4]).pipe(
70
+ Fx.skipWhileEffect((n) => Effect.succeed(n < 3)),
71
+ );
72
+ const result = yield* Fx.collectAll(fx);
73
+ assert.deepStrictEqual(result, [3, 4]);
74
+ }).pipe(Effect.scoped, Effect.runPromise));
75
+
76
+ it("skipWhileEffect fails when predicate effect fails", () =>
77
+ Effect.gen(function* () {
78
+ const fx = Fx.fromIterable([1, 2, 3]).pipe(
79
+ Fx.skipWhileEffect((n) => (n === 1 ? Effect.fail("err") : Effect.succeed(true))),
80
+ );
81
+ const exit = yield* Effect.exit(Fx.collectAll(fx));
82
+ assert(Exit.isFailure(exit));
83
+ }).pipe(Effect.scoped, Effect.runPromise));
84
+ });
@@ -0,0 +1,171 @@
1
+ import { assert, describe, it } from "vitest";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import { mergeLeft, mergeRight, zipLeft, zipRight } from "./Fx/combinators/additive.js";
5
+ import { Fx } from "./index.js";
6
+
7
+ describe("Fx zip/merge additive combinators", () => {
8
+ describe("zipLeft", () => {
9
+ it("emits only left values in lockstep; completes when first completes", () =>
10
+ Effect.gen(function* () {
11
+ const left = Fx.fromIterable([1, 2, 3]);
12
+ const right = Fx.fromIterable(["a", "b", "c", "d"]);
13
+ const result = yield* Fx.collectAll(zipLeft(left, right));
14
+ assert.deepStrictEqual(result, [1, 2, 3]);
15
+ }).pipe(Effect.scoped, Effect.runPromise));
16
+
17
+ it("completes when right is shorter (lockstep)", () =>
18
+ Effect.gen(function* () {
19
+ const left = Fx.fromIterable([1, 2, 3, 4]);
20
+ const right = Fx.fromIterable(["a", "b"]);
21
+ const result = yield* Fx.collectAll(zipLeft(left, right));
22
+ assert.deepStrictEqual(result, [1, 2]);
23
+ }).pipe(Effect.scoped, Effect.runPromise));
24
+
25
+ it("data-last form: pipe(self, zipLeft(that))", () =>
26
+ Effect.gen(function* () {
27
+ const result = yield* Fx.fromIterable([10, 20])
28
+ .pipe(zipLeft(Fx.fromIterable(["x", "y", "z"])))
29
+ .pipe(Fx.collectAll);
30
+ assert.deepStrictEqual(result, [10, 20]);
31
+ }).pipe(Effect.scoped, Effect.runPromise));
32
+
33
+ it("fails when left fails", () =>
34
+ Effect.gen(function* () {
35
+ const exit = yield* Effect.exit(
36
+ Fx.collectAll(zipLeft(Fx.fail("err" as const), Fx.fromIterable([1]))),
37
+ );
38
+ assert(Exit.isFailure(exit));
39
+ }).pipe(Effect.scoped, Effect.runPromise));
40
+ });
41
+
42
+ describe("zipRight", () => {
43
+ it("emits only right values in lockstep; completes when first completes", () =>
44
+ Effect.gen(function* () {
45
+ const left = Fx.fromIterable([1, 2, 3, 4]);
46
+ const right = Fx.fromIterable(["a", "b", "c"]);
47
+ const result = yield* Fx.collectAll(zipRight(left, right));
48
+ assert.deepStrictEqual(result, ["a", "b", "c"]);
49
+ }).pipe(Effect.scoped, Effect.runPromise));
50
+
51
+ it("completes when left is shorter (lockstep)", () =>
52
+ Effect.gen(function* () {
53
+ const left = Fx.fromIterable([1, 2]);
54
+ const right = Fx.fromIterable([10, 20, 30]);
55
+ const result = yield* Fx.collectAll(zipRight(left, right));
56
+ assert.deepStrictEqual(result, [10, 20]);
57
+ }).pipe(Effect.scoped, Effect.runPromise));
58
+
59
+ it("data-last form: pipe(self, zipRight(that))", () =>
60
+ Effect.gen(function* () {
61
+ const result = yield* Fx.fromIterable([1, 2, 3])
62
+ .pipe(zipRight(Fx.fromIterable(["a", "b"])))
63
+ .pipe(Fx.collectAll);
64
+ assert.deepStrictEqual(result, ["a", "b"]);
65
+ }).pipe(Effect.scoped, Effect.runPromise));
66
+
67
+ it("fails when right fails", () =>
68
+ Effect.gen(function* () {
69
+ const exit = yield* Effect.exit(
70
+ Fx.collectAll(zipRight(Fx.fromIterable([1]), Fx.fail("err" as const))),
71
+ );
72
+ assert(Exit.isFailure(exit));
73
+ }).pipe(Effect.scoped, Effect.runPromise));
74
+ });
75
+
76
+ describe("mergeLeft", () => {
77
+ it("emits only values from the left stream; both run concurrently", () =>
78
+ Effect.gen(function* () {
79
+ const left = Fx.fromIterable([1, 2, 3]);
80
+ const right = Fx.fromIterable(["a", "b"]);
81
+ const result = yield* Fx.collectAll(mergeLeft(left, right));
82
+ assert.strictEqual(result.length, 3);
83
+ assert.deepStrictEqual(
84
+ [...result].sort((a, b) => a - b),
85
+ [1, 2, 3],
86
+ );
87
+ }).pipe(Effect.scoped, Effect.runPromise));
88
+
89
+ it("completes when both streams complete", () =>
90
+ Effect.gen(function* () {
91
+ const left = Fx.fromIterable([1]);
92
+ const right = Fx.fromIterable([2, 3]);
93
+ const result = yield* Fx.collectAll(mergeLeft(left, right));
94
+ assert.deepStrictEqual(result, [1]);
95
+ }).pipe(Effect.scoped, Effect.runPromise));
96
+
97
+ it("emits left values only (order non-deterministic)", () =>
98
+ Effect.gen(function* () {
99
+ const left = Fx.fromIterable([10, 20]);
100
+ const right = Fx.fromIterable([30, 40]);
101
+ const result = yield* Fx.collectAll(mergeLeft(left, right));
102
+ assert.strictEqual(result.length, 2);
103
+ assert.ok(result.every((n) => n === 10 || n === 20));
104
+ }).pipe(Effect.scoped, Effect.runPromise));
105
+
106
+ it("fails when left fails", () =>
107
+ Effect.gen(function* () {
108
+ const exit = yield* Effect.exit(
109
+ Fx.collectAll(mergeLeft(Fx.fail("err" as const), Fx.fromIterable([1]))),
110
+ );
111
+ assert(Exit.isFailure(exit));
112
+ }).pipe(Effect.scoped, Effect.runPromise));
113
+
114
+ it("data-last form: pipe(self, mergeLeft(that))", () =>
115
+ Effect.gen(function* () {
116
+ const result = yield* Fx.fromIterable([1, 2])
117
+ .pipe(mergeLeft(Fx.fromIterable(["a", "b", "c"])))
118
+ .pipe(Fx.collectAll);
119
+ assert.strictEqual(result.length, 2);
120
+ assert.deepStrictEqual(
121
+ [...result].sort((a, b) => a - b),
122
+ [1, 2],
123
+ );
124
+ }).pipe(Effect.scoped, Effect.runPromise));
125
+ });
126
+
127
+ describe("mergeRight", () => {
128
+ it("emits only values from the right stream; both run concurrently", () =>
129
+ Effect.gen(function* () {
130
+ const left = Fx.fromIterable([1, 2]);
131
+ const right = Fx.fromIterable(["a", "b", "c"]);
132
+ const result = yield* Fx.collectAll(mergeRight(left, right));
133
+ assert.strictEqual(result.length, 3);
134
+ assert.deepStrictEqual([...result].sort(), ["a", "b", "c"]);
135
+ }).pipe(Effect.scoped, Effect.runPromise));
136
+
137
+ it("completes when both streams complete", () =>
138
+ Effect.gen(function* () {
139
+ const left = Fx.fromIterable([1, 2]);
140
+ const right = Fx.fromIterable([3]);
141
+ const result = yield* Fx.collectAll(mergeRight(left, right));
142
+ assert.deepStrictEqual(result, [3]);
143
+ }).pipe(Effect.scoped, Effect.runPromise));
144
+
145
+ it("emits right values only (order non-deterministic)", () =>
146
+ Effect.gen(function* () {
147
+ const left = Fx.fromIterable([1, 2]);
148
+ const right = Fx.fromIterable([10, 20]);
149
+ const result = yield* Fx.collectAll(mergeRight(left, right));
150
+ assert.strictEqual(result.length, 2);
151
+ assert.ok(result.every((n) => n === 10 || n === 20));
152
+ }).pipe(Effect.scoped, Effect.runPromise));
153
+
154
+ it("fails when right fails", () =>
155
+ Effect.gen(function* () {
156
+ const exit = yield* Effect.exit(
157
+ Fx.collectAll(mergeRight(Fx.fromIterable([1]), Fx.fail("err" as const))),
158
+ );
159
+ assert(Exit.isFailure(exit));
160
+ }).pipe(Effect.scoped, Effect.runPromise));
161
+
162
+ it("data-last form: pipe(self, mergeRight(that))", () =>
163
+ Effect.gen(function* () {
164
+ const result = yield* Fx.fromIterable([1, 2, 3])
165
+ .pipe(mergeRight(Fx.fromIterable(["x", "y"])))
166
+ .pipe(Fx.collectAll);
167
+ assert.strictEqual(result.length, 2);
168
+ assert.deepStrictEqual([...result].sort(), ["x", "y"]);
169
+ }).pipe(Effect.scoped, Effect.runPromise));
170
+ });
171
+ });