@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.
Files changed (139) 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/RefArray.d.ts.map +1 -1
  65. package/dist/RefSubject/RefArray.js +2 -1
  66. package/dist/RefSubject/RefChunk.d.ts.map +1 -1
  67. package/dist/RefSubject/RefChunk.js +2 -1
  68. package/dist/RefSubject/RefDateTime.d.ts +4 -4
  69. package/dist/RefSubject/RefDateTime.d.ts.map +1 -1
  70. package/dist/RefSubject/RefHashMap.d.ts.map +1 -1
  71. package/dist/RefSubject/RefHashMap.js +5 -1
  72. package/dist/RefSubject/RefIterable.d.ts +1 -1
  73. package/dist/RefSubject/RefIterable.d.ts.map +1 -1
  74. package/dist/RefSubject/RefIterable.js +6 -1
  75. package/dist/RefSubject/RefRecord.d.ts.map +1 -1
  76. package/dist/RefSubject/RefRecord.js +3 -2
  77. package/dist/RefSubject/RefSubject.d.ts +48 -1
  78. package/dist/RefSubject/RefSubject.d.ts.map +1 -1
  79. package/dist/RefSubject/RefSubject.js +80 -1
  80. package/dist/RefSubject/RefTrie.d.ts +7 -7
  81. package/dist/RefSubject/RefTrie.d.ts.map +1 -1
  82. package/dist/RefSubject/RefTrie.js +8 -3
  83. package/dist/Sink/combinators.d.ts +57 -0
  84. package/dist/Sink/combinators.d.ts.map +1 -1
  85. package/dist/Sink/combinators.js +104 -1
  86. package/dist/Versioned/Versioned.d.ts +30 -0
  87. package/dist/Versioned/Versioned.d.ts.map +1 -1
  88. package/dist/Versioned/Versioned.js +18 -0
  89. package/package.json +10 -6
  90. package/src/Fx/combinators/additive.ts +142 -0
  91. package/src/Fx/combinators/catch.ts +256 -0
  92. package/src/Fx/combinators/changesWithEffect.ts +66 -0
  93. package/src/Fx/combinators/dropUntil.ts +47 -0
  94. package/src/Fx/combinators/flatMapConcurrently.ts +5 -2
  95. package/src/Fx/combinators/index.ts +10 -0
  96. package/src/Fx/combinators/keyed.ts +2 -2
  97. package/src/Fx/combinators/mapBoth.ts +40 -0
  98. package/src/Fx/combinators/mapError.ts +28 -0
  99. package/src/Fx/combinators/provide.ts +63 -1
  100. package/src/Fx/combinators/result.ts +39 -0
  101. package/src/Fx/combinators/scan.ts +82 -0
  102. package/src/Fx/combinators/skip.ts +21 -0
  103. package/src/Fx/combinators/skipWhile.ts +100 -0
  104. package/src/Fx/combinators/slice.ts +23 -0
  105. package/src/Fx/combinators/take.ts +21 -0
  106. package/src/Fx/combinators/takeUntil.ts +38 -0
  107. package/src/Fx/combinators/takeWhile.ts +47 -0
  108. package/src/Fx/combinators/zip.ts +175 -0
  109. package/src/Fx/constructors/at.ts +3 -3
  110. package/src/Fx/constructors/periodic.ts +1 -1
  111. package/src/Fx.additive-combinators.test.ts +126 -0
  112. package/src/Fx.catch-additive.test.ts +206 -0
  113. package/src/Fx.catch.test.ts +1 -2
  114. package/src/Fx.dropUntil.test.ts +61 -0
  115. package/src/Fx.lifecycle.test.ts +1 -2
  116. package/src/Fx.mapError-mapBoth.test.ts +101 -0
  117. package/src/Fx.provide-combinators.test.ts +94 -0
  118. package/src/Fx.result-changesWithEffect.test.ts +112 -0
  119. package/src/Fx.scan.test.ts +73 -0
  120. package/src/Fx.takeWhile-skipWhile.test.ts +84 -0
  121. package/src/Fx.zip-merge-additive.test.ts +171 -0
  122. package/src/Fx.zip.test.ts +133 -0
  123. package/src/Push/Push.ts +170 -1
  124. package/src/Push.additive.test.ts +256 -0
  125. package/src/RefSubject/RefArray.ts +4 -1
  126. package/src/RefSubject/RefChunk.ts +2 -1
  127. package/src/RefSubject/RefDateTime.ts +6 -6
  128. package/src/RefSubject/RefHashMap.ts +10 -1
  129. package/src/RefSubject/RefIterable.ts +11 -2
  130. package/src/RefSubject/RefRecord.ts +9 -2
  131. package/src/RefSubject/RefSubject.ts +108 -9
  132. package/src/RefSubject/RefTrie.ts +19 -10
  133. package/src/RefSubject.additive-parity.test.ts +101 -0
  134. package/src/Sink/combinators.ts +123 -1
  135. package/src/Sink.combinators.test.ts +88 -0
  136. package/src/Sink.reduce-collect-head-last.test.ts +107 -0
  137. package/src/Versioned/Versioned.ts +76 -0
  138. package/src/Versioned.filterMap.test.ts +91 -0
  139. package/tsconfig.json +0 -6
@@ -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
+ });
@@ -0,0 +1,133 @@
1
+ import { assert, describe, it } from "vitest";
2
+ import * as Effect from "effect/Effect";
3
+ import { Fx } from "./index.js";
4
+ import * as Sink from "./Sink/index.js";
5
+
6
+ describe("Fx zip combinators", () => {
7
+ describe("zip", () => {
8
+ it("emits pairs when run directly with a sink (no observe)", () =>
9
+ Effect.gen(function* () {
10
+ const arr: Array<readonly [number, string]> = [];
11
+ const sink = Sink.make(
12
+ () => Effect.void,
13
+ (pair: readonly [number, string]) => Effect.sync(() => arr.push(pair)),
14
+ );
15
+ yield* Fx.zip(Fx.fromIterable([1, 2, 3]), Fx.fromIterable(["a", "b", "c", "d"])).run(sink);
16
+ assert.deepStrictEqual(arr, [
17
+ [1, "a"],
18
+ [2, "b"],
19
+ [3, "c"],
20
+ ]);
21
+ }).pipe(Effect.runPromise));
22
+
23
+ it("emits pairs in lockstep; completes when first stream completes", () =>
24
+ Effect.gen(function* () {
25
+ const left = Fx.fromIterable([1, 2, 3]);
26
+ const right = Fx.fromIterable(["a", "b", "c", "d"]);
27
+ const result = yield* Fx.collectAll(Fx.zip(left, right));
28
+ assert.deepStrictEqual(result, [
29
+ [1, "a"],
30
+ [2, "b"],
31
+ [3, "c"],
32
+ ]);
33
+ }).pipe(Effect.scoped, Effect.runPromise));
34
+
35
+ it("completes when right is shorter than left", () =>
36
+ Effect.gen(function* () {
37
+ const left = Fx.fromIterable([1, 2, 3, 4]);
38
+ const right = Fx.fromIterable(["a", "b"]);
39
+ const result = yield* Fx.collectAll(Fx.zip(left, right));
40
+ assert.deepStrictEqual(result, [
41
+ [1, "a"],
42
+ [2, "b"],
43
+ ]);
44
+ }).pipe(Effect.scoped, Effect.runPromise));
45
+
46
+ it("data-last form: pipe(self, Fx.zip(that))", () =>
47
+ Effect.gen(function* () {
48
+ const result = yield* Fx.fromIterable([1, 2])
49
+ .pipe(Fx.zip(Fx.fromIterable(["x", "y"])))
50
+ .pipe(Fx.collectAll);
51
+ assert.deepStrictEqual(result, [
52
+ [1, "x"],
53
+ [2, "y"],
54
+ ]);
55
+ }).pipe(Effect.scoped, Effect.runPromise));
56
+ });
57
+
58
+ describe("zipWith", () => {
59
+ it("emits f(a,b) in lockstep", () =>
60
+ Effect.gen(function* () {
61
+ const left = Fx.fromIterable([1, 2, 3]);
62
+ const right = Fx.fromIterable([10, 20, 30]);
63
+ const result = yield* Fx.collectAll(Fx.zipWith(left, right, (a, b) => a + b));
64
+ assert.deepStrictEqual(result, [11, 22, 33]);
65
+ }).pipe(Effect.scoped, Effect.runPromise));
66
+
67
+ it("completes when first stream completes", () =>
68
+ Effect.gen(function* () {
69
+ const result = yield* Fx.collectAll(
70
+ Fx.zipWith(Fx.fromIterable([1, 2, 3]), Fx.fromIterable([0, 0]), (a, b) => a + b),
71
+ );
72
+ assert.deepStrictEqual(result, [1, 2]);
73
+ }).pipe(Effect.scoped, Effect.runPromise));
74
+
75
+ it("data-last form: pipe(self, Fx.zipWith(that, f))", () =>
76
+ Effect.gen(function* () {
77
+ const result = yield* Fx.fromIterable(["a", "b"])
78
+ .pipe(Fx.zipWith(Fx.fromIterable([1, 2]), (s, n) => `${s}${n}`))
79
+ .pipe(Fx.collectAll);
80
+ assert.deepStrictEqual(result, ["a1", "b2"]);
81
+ }).pipe(Effect.scoped, Effect.runPromise));
82
+ });
83
+
84
+ describe("zipLatest", () => {
85
+ it("waits for both to emit once then emits on every update from either", () =>
86
+ Effect.gen(function* () {
87
+ const left = Fx.fromIterable([1, 2, 3]);
88
+ const right = Fx.fromIterable(["a", "b"]);
89
+ const result = yield* Fx.collectAll(Fx.zipLatest(left, right));
90
+ // tuple runs both streams; emission order depends on scheduling.
91
+ assert.ok(result.length >= 2, "at least two emissions");
92
+ assert.ok(
93
+ result.some((p) => p[0] === 3 && p[1] === "b"),
94
+ "should contain [3, 'b']",
95
+ );
96
+ }).pipe(Effect.scoped, Effect.runPromise));
97
+
98
+ it("data-last form: pipe(self, Fx.zipLatest(that))", () =>
99
+ Effect.gen(function* () {
100
+ const result = yield* Fx.fromIterable([1, 2])
101
+ .pipe(Fx.zipLatest(Fx.fromIterable(["a", "b"])))
102
+ .pipe(Fx.collectAll);
103
+ assert.ok(result.length >= 2);
104
+ assert.deepStrictEqual(result[result.length - 1], [2, "b"]);
105
+ }).pipe(Effect.scoped, Effect.runPromise));
106
+ });
107
+
108
+ describe("zipLatestWith", () => {
109
+ it("emits f(left, right) on every update from either stream", () =>
110
+ Effect.gen(function* () {
111
+ const result = yield* Fx.collectAll(
112
+ Fx.zipLatestWith(Fx.fromIterable([1, 2]), Fx.fromIterable([10, 20]), (a, b) => a + b),
113
+ );
114
+ assert.ok(result.length >= 2);
115
+ // First emission depends on which stream fills first (1+10=11 or 2+10=12 or 1+20=21)
116
+ assert.ok([11, 12, 21, 22].includes(result[0]));
117
+ }).pipe(Effect.scoped, Effect.runPromise));
118
+
119
+ it("data-last form: pipe(self, Fx.zipLatestWith(that, f))", () =>
120
+ Effect.gen(function* () {
121
+ const result = yield* Fx.fromIterable([1, 2])
122
+ .pipe(Fx.zipLatestWith(Fx.fromIterable(["a", "b"]), (n, s) => `${n}${s}`))
123
+ .pipe(Fx.collectAll);
124
+ assert.ok(result.length >= 2);
125
+ // First emission is combined first values; last includes latest from both
126
+ assert.ok(
127
+ result.includes("1a") || result.includes("2a") || result.includes("2b"),
128
+ "should contain a combined pair",
129
+ );
130
+ assert.ok(result.includes("2b"), "should contain final pair 2b");
131
+ }).pipe(Effect.scoped, Effect.runPromise));
132
+ });
133
+ });