@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
package/src/Push/Push.ts CHANGED
@@ -8,7 +8,7 @@ import type * as Cause from "effect/Cause";
8
8
  import * as Effect from "effect/Effect";
9
9
  import { dual, identity } from "effect/Function";
10
10
  import * as Layer from "effect/Layer";
11
- import type * as Option from "effect/Option";
11
+ import * as Option from "effect/Option";
12
12
  import { pipeArguments } from "effect/Pipeable";
13
13
  import type * as Scope from "effect/Scope";
14
14
  import * as ServiceMap from "effect/ServiceMap";
@@ -432,6 +432,68 @@ export const mapEffect: {
432
432
  return make(push, Fx.mapEffect(push, f));
433
433
  });
434
434
 
435
+ /**
436
+ * Transforms the output (Fx) error channel of a `Push` using the provided function.
437
+ *
438
+ * Failures (Cause) are mapped via `Cause.map`, so only the typed failure (`Fail`)
439
+ * is transformed; defects and interrupts are preserved unchanged.
440
+ *
441
+ * Mirrors `Effect.mapError` on the Fx side.
442
+ *
443
+ * @since 1.0.0
444
+ * @category combinators
445
+ */
446
+ export const mapError: {
447
+ <E2, E3>(
448
+ f: (e: E2) => E3,
449
+ ): <A, E, R, B, R2>(push: Push<A, E, R, B, E2, R2>) => Push<A, E, R, B, E3, R2>;
450
+ <A, E, R, B, E2, R2, E3>(
451
+ push: Push<A, E, R, B, E2, R2>,
452
+ f: (e: E2) => E3,
453
+ ): Push<A, E, R, B, E3, R2>;
454
+ } = dual(2, function mapError<
455
+ A,
456
+ E,
457
+ R,
458
+ B,
459
+ E2,
460
+ R2,
461
+ E3,
462
+ >(push: Push<A, E, R, B, E2, R2>, f: (e: E2) => E3): Push<A, E, R, B, E3, R2> {
463
+ return make(push, Fx.mapError(push, f));
464
+ });
465
+
466
+ /**
467
+ * Transforms both the output (Fx) success and error channels of a `Push` using the provided options.
468
+ *
469
+ * Mirrors `Effect.mapBoth` on the Fx side: `onSuccess` maps emitted values,
470
+ * `onFailure` maps the typed failure (via `Cause.map`); defects and interrupts are preserved.
471
+ *
472
+ * @since 1.0.0
473
+ * @category combinators
474
+ */
475
+ export const mapBoth: {
476
+ <B, C, E2, E3>(options: {
477
+ readonly onFailure: (e: E2) => E3;
478
+ readonly onSuccess: (b: B) => C;
479
+ }): <A, E, R, R2>(push: Push<A, E, R, B, E2, R2>) => Push<A, E, R, C, E3, R2>;
480
+ <A, E, R, B, E2, R2, C, E3>(
481
+ push: Push<A, E, R, B, E2, R2>,
482
+ options: { readonly onFailure: (e: E2) => E3; readonly onSuccess: (b: B) => C },
483
+ ): Push<A, E, R, C, E3, R2>;
484
+ } = dual(
485
+ 2,
486
+ function mapBoth<A, E, R, B, E2, R2, C, E3>(
487
+ push: Push<A, E, R, B, E2, R2>,
488
+ options: {
489
+ readonly onFailure: (e: E2) => E3;
490
+ readonly onSuccess: (b: B) => C;
491
+ },
492
+ ): Push<A, E, R, C, E3, R2> {
493
+ return make(push, Fx.mapBoth(push, options));
494
+ },
495
+ );
496
+
435
497
  export const filter: {
436
498
  <B>(
437
499
  f: (b: B) => boolean,
@@ -844,6 +906,113 @@ export const exhaustLatestMapEffect: {
844
906
  return make(push, Fx.exhaustLatestMapEffect(push, f));
845
907
  });
846
908
 
909
+ /**
910
+ * Maps over the output (Fx) side of a `Push` with an accumulator: for each emitted value `b`,
911
+ * applies `f(state, b)` to get `[nextState, emitted]` and emits the second element.
912
+ * The first element is the initial state; subsequent states are updated by each step.
913
+ *
914
+ * @param initial - Initial accumulator state.
915
+ * @param f - Reducer `(state, value) => [nextState, emitted]`.
916
+ * @returns A `Push` whose Fx side emits the accumulated/mapped values.
917
+ * @since 1.0.0
918
+ * @category combinators
919
+ */
920
+ export const mapAccum: {
921
+ <S, B, C>(
922
+ initial: S,
923
+ f: (s: S, b: B) => readonly [S, C],
924
+ ): <A, E, R, E2, R2>(push: Push<A, E, R, B, E2, R2>) => Push<A, E, R, C, E2, R2>;
925
+ <A, E, R, B, E2, R2, S, C>(
926
+ push: Push<A, E, R, B, E2, R2>,
927
+ initial: S,
928
+ f: (s: S, b: B) => readonly [S, C],
929
+ ): Push<A, E, R, C, E2, R2>;
930
+ } = dual(3, function mapAccum<
931
+ A,
932
+ E,
933
+ R,
934
+ B,
935
+ E2,
936
+ R2,
937
+ S,
938
+ C,
939
+ >(push: Push<A, E, R, B, E2, R2>, initial: S, f: (s: S, b: B) => readonly [S, C]): Push<
940
+ A,
941
+ E,
942
+ R,
943
+ C,
944
+ E2,
945
+ R2
946
+ > {
947
+ return make(
948
+ push,
949
+ Fx.make((sink) =>
950
+ push.run(
951
+ Sink.loop(sink, initial, (s, b) => {
952
+ const [sNext, c] = f(s, b);
953
+ return [c, sNext];
954
+ }),
955
+ ),
956
+ ),
957
+ );
958
+ });
959
+
960
+ /**
961
+ * Maps over the output (Fx) side of a `Push` with an effectful accumulator: for each emitted value `b`,
962
+ * runs `f(state, b)` to get `[nextState, emitted]` and emits the second element.
963
+ *
964
+ * @param initial - Initial accumulator state.
965
+ * @param f - Effectful reducer `(state, value) => Effect<[nextState, emitted]>`.
966
+ * @returns A `Push` whose Fx side emits the accumulated/mapped values.
967
+ * @since 1.0.0
968
+ * @category combinators
969
+ */
970
+ export const mapAccumEffect: {
971
+ <S, B, C, E3, R3>(
972
+ initial: S,
973
+ f: (s: S, b: B) => Effect.Effect<readonly [S, C], E3, R3>,
974
+ ): <A, E, R, E2, R2>(push: Push<A, E, R, B, E2, R2>) => Push<A, E, R, C, E2 | E3, R2 | R3>;
975
+ <A, E, R, B, E2, R2, S, C, E3, R3>(
976
+ push: Push<A, E, R, B, E2, R2>,
977
+ initial: S,
978
+ f: (s: S, b: B) => Effect.Effect<readonly [S, C], E3, R3>,
979
+ ): Push<A, E, R, C, E2 | E3, R2 | R3>;
980
+ } = dual(3, function mapAccumEffect<
981
+ A,
982
+ E,
983
+ R,
984
+ B,
985
+ E2,
986
+ R2,
987
+ S,
988
+ C,
989
+ E3,
990
+ R3,
991
+ >(push: Push<A, E, R, B, E2, R2>, initial: S, f: (s: S, b: B) => Effect.Effect<readonly [S, C], E3, R3>): Push<
992
+ A,
993
+ E,
994
+ R,
995
+ C,
996
+ E2 | E3,
997
+ R2 | R3
998
+ > {
999
+ return make(
1000
+ push,
1001
+ Fx.make(<RSink>(sink: Sink.Sink<C, E2 | E3, RSink>) =>
1002
+ push.run(
1003
+ Sink.filterMapLoopEffect(sink, initial, (s, b) =>
1004
+ f(s, b).pipe(
1005
+ Effect.map(([sNext, c]) => [Option.some(c), sNext] as const),
1006
+ Effect.catchCause((cause) =>
1007
+ sink.onFailure(cause).pipe(Effect.as([Option.none(), s] as const)),
1008
+ ),
1009
+ ),
1010
+ ),
1011
+ ),
1012
+ ),
1013
+ );
1014
+ });
1015
+
847
1016
  export function Service<Self, A, E = never, B = never, E2 = never>() {
848
1017
  return <const Id extends string>(id: Id): Push.Class<Self, Id, A, E, B, E2> => {
849
1018
  const service = ServiceMap.Service<Self, Push<A, E, never, B, E2, never>>(id);
@@ -0,0 +1,256 @@
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, Push, Sink } from "./index.js";
6
+
7
+ describe("Push.mapError", () => {
8
+ it("maps Fx output error channel to new type", () =>
9
+ Effect.gen(function* () {
10
+ const push = Push.make(
11
+ Sink.make(
12
+ () => Effect.void,
13
+ () => Effect.void,
14
+ ),
15
+ Fx.fail("err"),
16
+ );
17
+ const mapped = Push.mapError(push, (s) => ({ msg: s }));
18
+ const exit = yield* Effect.exit(Fx.collectAll(mapped));
19
+ assert(Exit.isFailure(exit));
20
+ const result = Cause.findFail(exit.cause);
21
+ assert(result._tag === "Success");
22
+ assert.deepStrictEqual(result.success.error, { msg: "err" });
23
+ }).pipe(Effect.scoped, Effect.runPromise));
24
+
25
+ it("dual call style: data-last", () =>
26
+ Effect.gen(function* () {
27
+ const push = Push.make(
28
+ Sink.make(
29
+ () => Effect.void,
30
+ () => Effect.void,
31
+ ),
32
+ Fx.fail(42),
33
+ );
34
+ const mapped = Push.mapError(push, (n) => `n=${n}`);
35
+ const exit = yield* Effect.exit(Fx.collectAll(mapped));
36
+ assert(Exit.isFailure(exit));
37
+ const result = Cause.findFail(exit.cause);
38
+ assert(result._tag === "Success");
39
+ assert.strictEqual(result.success.error, "n=42");
40
+ }).pipe(Effect.scoped, Effect.runPromise));
41
+
42
+ it("preserves success values on Fx side", () =>
43
+ Effect.gen(function* () {
44
+ const push = Push.make(
45
+ Sink.make(
46
+ () => Effect.void,
47
+ () => Effect.void,
48
+ ),
49
+ Fx.fromIterable([1, 2, 3]),
50
+ );
51
+ const mapped = push.pipe(Push.mapError((e: string) => e));
52
+ const result = yield* Fx.collectAll(mapped);
53
+ assert.deepStrictEqual(result, [1, 2, 3]);
54
+ }).pipe(Effect.scoped, Effect.runPromise));
55
+
56
+ it("does not map defects (Cause.map only maps Fail)", () =>
57
+ Effect.gen(function* () {
58
+ const push = Push.make(
59
+ Sink.make(
60
+ () => Effect.void,
61
+ () => Effect.void,
62
+ ),
63
+ Fx.die("defect"),
64
+ );
65
+ const mapped = Push.mapError(push, (_e: string) => "mapped");
66
+ const exit = yield* Effect.exit(Fx.collectAll(mapped));
67
+ assert(Exit.isFailure(exit));
68
+ assert(Cause.hasDies(exit.cause));
69
+ }).pipe(Effect.scoped, Effect.runPromise));
70
+ });
71
+
72
+ describe("Push.mapBoth", () => {
73
+ it("maps both Fx success and error channels", () =>
74
+ Effect.gen(function* () {
75
+ const push = Push.make(
76
+ Sink.make(
77
+ () => Effect.void,
78
+ () => Effect.void,
79
+ ),
80
+ Fx.fromIterable([1, 2, 3]),
81
+ );
82
+ const mapped = Push.mapBoth(push, {
83
+ onSuccess: (n) => n * 10,
84
+ onFailure: (e: string) => e.length,
85
+ });
86
+ const result = yield* Fx.collectAll(mapped);
87
+ assert.deepStrictEqual(result, [10, 20, 30]);
88
+ }).pipe(Effect.scoped, Effect.runPromise));
89
+
90
+ it("maps Fx failure when stream fails", () =>
91
+ Effect.gen(function* () {
92
+ const push = Push.make(
93
+ Sink.make(
94
+ () => Effect.void,
95
+ () => Effect.void,
96
+ ),
97
+ Fx.fail("boom"),
98
+ );
99
+ const mapped = Push.mapBoth(push, {
100
+ onSuccess: (a: number) => a,
101
+ onFailure: (s: string) => ({ message: s }),
102
+ });
103
+ const exit = yield* Effect.exit(Fx.collectAll(mapped));
104
+ assert(Exit.isFailure(exit));
105
+ const result = Cause.findFail(exit.cause);
106
+ assert(result._tag === "Success");
107
+ assert.deepStrictEqual(result.success.error, { message: "boom" });
108
+ }).pipe(Effect.scoped, Effect.runPromise));
109
+
110
+ it("dual call style: data-last", () =>
111
+ Effect.gen(function* () {
112
+ const push = Push.make(
113
+ Sink.make(
114
+ () => Effect.void,
115
+ () => Effect.void,
116
+ ),
117
+ Fx.fromIterable(["a", "b"]),
118
+ );
119
+ const mapped = Push.mapBoth(push, {
120
+ onSuccess: (s) => s.toUpperCase(),
121
+ onFailure: (e: number) => String(e),
122
+ });
123
+ const result = yield* Fx.collectAll(mapped);
124
+ assert.deepStrictEqual(result, ["A", "B"]);
125
+ }).pipe(Effect.scoped, Effect.runPromise));
126
+
127
+ it("preserves defects (onFailure only maps Fail)", () =>
128
+ Effect.gen(function* () {
129
+ const push = Push.make(
130
+ Sink.make(
131
+ () => Effect.void,
132
+ () => Effect.void,
133
+ ),
134
+ Fx.die("oops"),
135
+ );
136
+ const mapped = Push.mapBoth(push, {
137
+ onSuccess: (a: number) => a,
138
+ onFailure: (_e: string) => "mapped",
139
+ });
140
+ const exit = yield* Effect.exit(Fx.collectAll(mapped));
141
+ assert(Exit.isFailure(exit));
142
+ assert(Cause.hasDies(exit.cause));
143
+ }).pipe(Effect.scoped, Effect.runPromise));
144
+ });
145
+
146
+ describe("Push.mapAccum", () => {
147
+ it("emits accumulated values from (state, value) => [nextState, emitted]", () =>
148
+ Effect.gen(function* () {
149
+ const push = Push.make(
150
+ Sink.make(
151
+ () => Effect.void,
152
+ () => Effect.void,
153
+ ),
154
+ Fx.fromIterable([1, 2, 3, 4]),
155
+ );
156
+ const accumulated = Push.mapAccum(push, 0, (sum, n) => [sum + n, sum + n] as const);
157
+ const result = yield* Fx.collectAll(accumulated);
158
+ assert.deepStrictEqual(result, [1, 3, 6, 10]);
159
+ }).pipe(Effect.scoped, Effect.runPromise));
160
+
161
+ it("data-last form: pipe(push, mapAccum(initial, f))", () =>
162
+ Effect.gen(function* () {
163
+ const push = Push.make(
164
+ Sink.make(
165
+ () => Effect.void,
166
+ () => Effect.void,
167
+ ),
168
+ Fx.fromIterable(["a", "b", "c"]),
169
+ );
170
+ const accumulated = push.pipe(
171
+ Push.mapAccum("", (acc, s) => [`${acc}${s}`, `${acc}${s}`] as const),
172
+ );
173
+ const result = yield* Fx.collectAll(accumulated);
174
+ assert.deepStrictEqual(result, ["a", "ab", "abc"]);
175
+ }).pipe(Effect.scoped, Effect.runPromise));
176
+
177
+ it("data-first form: mapAccum(push, initial, f)", () =>
178
+ Effect.gen(function* () {
179
+ const push = Push.make(
180
+ Sink.make(
181
+ () => Effect.void,
182
+ () => Effect.void,
183
+ ),
184
+ Fx.fromIterable([2, 3, 4]),
185
+ );
186
+ const accumulated = Push.mapAccum(push, 1, (prod, n) => [prod * n, prod * n] as const);
187
+ const result = yield* Fx.collectAll(accumulated);
188
+ assert.deepStrictEqual(result, [2, 6, 24]);
189
+ }).pipe(Effect.scoped, Effect.runPromise));
190
+
191
+ it("single value emits one accumulated result", () =>
192
+ Effect.gen(function* () {
193
+ const push = Push.make(
194
+ Sink.make(
195
+ () => Effect.void,
196
+ () => Effect.void,
197
+ ),
198
+ Fx.succeed(42),
199
+ );
200
+ const accumulated = Push.mapAccum(push, 0, (s, n) => [s + n, n] as const);
201
+ const result = yield* Fx.collectAll(accumulated);
202
+ assert.deepStrictEqual(result, [42]);
203
+ }).pipe(Effect.scoped, Effect.runPromise));
204
+ });
205
+
206
+ describe("Push.mapAccumEffect", () => {
207
+ it("emits accumulated values from effectful reducer", () =>
208
+ Effect.gen(function* () {
209
+ const push = Push.make(
210
+ Sink.make(
211
+ () => Effect.void,
212
+ () => Effect.void,
213
+ ),
214
+ Fx.fromIterable([1, 2, 3]),
215
+ );
216
+ const accumulated = Push.mapAccumEffect(push, 0, (sum, n) =>
217
+ Effect.succeed([sum + n, sum + n] as const),
218
+ );
219
+ const result = yield* Fx.collectAll(accumulated);
220
+ assert.deepStrictEqual(result, [1, 3, 6]);
221
+ }).pipe(Effect.scoped, Effect.runPromise));
222
+
223
+ it("data-first form: mapAccumEffect(push, initial, f)", () =>
224
+ Effect.gen(function* () {
225
+ const push = Push.make(
226
+ Sink.make(
227
+ () => Effect.void,
228
+ () => Effect.void,
229
+ ),
230
+ Fx.fromIterable([10, 20]),
231
+ );
232
+ const accumulated = Push.mapAccumEffect(push, 1, (acc, n) =>
233
+ Effect.succeed([acc * n, acc * n] as const),
234
+ );
235
+ const result = yield* Fx.collectAll(accumulated);
236
+ assert.deepStrictEqual(result, [10, 200]);
237
+ }).pipe(Effect.scoped, Effect.runPromise));
238
+
239
+ it("propagates effect failure", () =>
240
+ Effect.gen(function* () {
241
+ const push = Push.make(
242
+ Sink.make(
243
+ () => Effect.void,
244
+ () => Effect.void,
245
+ ),
246
+ Fx.fromIterable([1, 2, 3]),
247
+ );
248
+ const accumulated = Push.mapAccumEffect(push, 0, (sum, n) =>
249
+ n === 2 ? Effect.fail("stop" as const) : Effect.succeed([sum + n, sum + n] as const),
250
+ );
251
+ const exit = yield* Effect.exit(Fx.collectAll(accumulated));
252
+ assert(Exit.isFailure(exit));
253
+ const fail = Cause.findFail(exit.cause);
254
+ assert(fail._tag === "Success" && fail.success.error === "stop");
255
+ }).pipe(Effect.scoped, Effect.runPromise));
256
+ });
@@ -14,6 +14,7 @@ import type * as Order from "effect/Order";
14
14
  import type * as Scope from "effect/Scope";
15
15
  import type * as Fx from "../Fx/index.js";
16
16
  import * as RefSubject from "./RefSubject.js";
17
+ import { Filter, Result } from "effect";
17
18
 
18
19
  /**
19
20
  * A RefArray is a RefSubject that is specialized over an array of values.
@@ -310,7 +311,9 @@ export const partition: {
310
311
  predicate: (a: A) => boolean,
311
312
  ): RefSubject.Computed<never, E, readonly [ReadonlyArray<A>, ReadonlyArray<A>]>;
312
313
  } = dual(2, function partition<A, E, R>(ref: RefArray<A, E, R>, predicate: (a: A) => boolean) {
313
- return RefSubject.map(ref, ReadonlyArray.partition(predicate));
314
+ return RefSubject.map(ref, (array) =>
315
+ ReadonlyArray.partition(array, Result.liftPredicate(predicate, Result.fail)),
316
+ );
314
317
  });
315
318
 
316
319
  /**
@@ -12,6 +12,7 @@ import type * as Order from "effect/Order";
12
12
  import type * as Scope from "effect/Scope";
13
13
  import type * as Fx from "../Fx/index.js";
14
14
  import * as RefSubject from "./RefSubject.js";
15
+ import { Result } from "effect";
15
16
 
16
17
  /**
17
18
  * A RefChunk is a RefSubject specialized over a Chunk of values.
@@ -356,7 +357,7 @@ export const partition: {
356
357
  predicate: (a: A) => boolean,
357
358
  ): RefSubject.Computed<[Chunk.Chunk<A>, Chunk.Chunk<A>], E, R>;
358
359
  } = dual(2, function partition<A, E, R>(ref: RefChunk<A, E, R>, predicate: (a: A) => boolean) {
359
- return RefSubject.map(ref, Chunk.partition(predicate));
360
+ return RefSubject.map(ref, Chunk.partition(Result.liftPredicate(predicate, Result.fail)));
360
361
  });
361
362
 
362
363
  /**
@@ -110,13 +110,13 @@ export const add: {
110
110
  */
111
111
  export const addDuration: {
112
112
  (
113
- duration: Duration.DurationInput,
113
+ duration: Duration.Input,
114
114
  ): <E, R>(ref: RefDateTime<E, R>) => RefSubject.Computed<DateTime.DateTime, E, R>;
115
115
  <E, R>(
116
116
  ref: RefDateTime<E, R>,
117
- duration: Duration.DurationInput,
117
+ duration: Duration.Input,
118
118
  ): RefSubject.Computed<DateTime.DateTime, E, R>;
119
- } = dual(2, function addDuration<E, R>(ref: RefDateTime<E, R>, duration: Duration.DurationInput) {
119
+ } = dual(2, function addDuration<E, R>(ref: RefDateTime<E, R>, duration: Duration.Input) {
120
120
  return RefSubject.map(ref, (self) => DateTime.addDuration(self, duration));
121
121
  });
122
122
 
@@ -177,16 +177,16 @@ export const subtract: {
177
177
  */
178
178
  export const subtractDuration: {
179
179
  (
180
- duration: Duration.DurationInput,
180
+ duration: Duration.Input,
181
181
  ): <E, R>(ref: RefDateTime<E, R>) => RefSubject.Computed<DateTime.DateTime, E, R>;
182
182
  <E, R>(
183
183
  ref: RefDateTime<E, R>,
184
- duration: Duration.DurationInput,
184
+ duration: Duration.Input,
185
185
  ): RefSubject.Computed<DateTime.DateTime, E, R>;
186
186
  } = dual(2, function subtractDuration<
187
187
  E,
188
188
  R,
189
- >(ref: RefDateTime<E, R>, duration: Duration.DurationInput) {
189
+ >(ref: RefDateTime<E, R>, duration: Duration.Input) {
190
190
  return RefSubject.map(ref, (self) => DateTime.subtractDuration(self, duration));
191
191
  });
192
192
 
@@ -11,6 +11,7 @@ import * as Option from "effect/Option";
11
11
  import type * as Scope from "effect/Scope";
12
12
  import type * as Fx from "../Fx/index.js";
13
13
  import * as RefSubject from "./RefSubject.js";
14
+ import { Result } from "effect";
14
15
 
15
16
  /**
16
17
  * A RefHashMap is a RefSubject specialized over a HashMap.
@@ -354,7 +355,15 @@ export const filterMapValues: {
354
355
  R,
355
356
  B,
356
357
  >(ref: RefHashMap<K, V, E, R>, f: (value: V, key: K) => Option.Option<B>) {
357
- return RefSubject.map(ref, HashMap.filterMap(f));
358
+ return RefSubject.map(
359
+ ref,
360
+ HashMap.filterMap((value, key) =>
361
+ Option.match(f(value, key), {
362
+ onNone: () => Result.failVoid,
363
+ onSome: (b) => Result.succeed(b),
364
+ }),
365
+ ),
366
+ );
358
367
  });
359
368
 
360
369
  /**
@@ -3,11 +3,12 @@
3
3
  * @since 1.18.0
4
4
  */
5
5
 
6
+ import { Result } from "effect";
6
7
  import type * as Effect from "effect/Effect";
7
8
  import { equals } from "effect/Equal";
8
9
  import { dual } from "effect/Function";
9
10
  import * as Iterable from "effect/Iterable";
10
- import type * as Option from "effect/Option";
11
+ import * as Option from "effect/Option";
11
12
  import type * as Scope from "effect/Scope";
12
13
  import type * as Fx from "../Fx/index.js";
13
14
  import * as RefSubject from "./RefSubject.js";
@@ -233,7 +234,15 @@ export const filterMap: {
233
234
  E,
234
235
  R,
235
236
  >(ref: RefIterable<A, E, R>, f: (a: A, index: number) => Option.Option<A>) {
236
- return RefSubject.update(ref, Iterable.filterMap(f));
237
+ return RefSubject.update(
238
+ ref,
239
+ Iterable.filterMap((a, index) =>
240
+ Option.match(f(a, index), {
241
+ onNone: () => Result.failVoid,
242
+ onSome: (b) => Result.succeed(b),
243
+ }),
244
+ ),
245
+ );
237
246
  });
238
247
 
239
248
  /**
@@ -3,6 +3,7 @@
3
3
  * @since 1.18.0
4
4
  */
5
5
 
6
+ import { Result } from "effect";
6
7
  import type * as Effect from "effect/Effect";
7
8
  import { equals } from "effect/Equal";
8
9
  import { dual } from "effect/Function";
@@ -450,7 +451,11 @@ export const filterMapValues: {
450
451
  R,
451
452
  B,
452
453
  >(ref: RefRecord<K, V, E, R>, f: (value: V, key: K) => Option.Option<B>) {
453
- return RefSubject.map(ref, (r) => Record.filterMap(r, f) as Record.ReadonlyRecord<K, B>);
454
+ return RefSubject.map(ref, (r) =>
455
+ Record.filterMap(r, (value, key) =>
456
+ f(value, key) ? Result.succeed(value) : Result.fail(value),
457
+ ),
458
+ );
454
459
  });
455
460
 
456
461
  /**
@@ -477,7 +482,9 @@ export const partition: {
477
482
  return RefSubject.map(
478
483
  ref,
479
484
  (r) =>
480
- Record.partition(r, predicate) as [Record.ReadonlyRecord<K, V>, Record.ReadonlyRecord<K, V>],
485
+ Record.partition(r, (value, key) =>
486
+ predicate(value, key) ? Result.succeed(value) : Result.fail(value),
487
+ ) as [Record.ReadonlyRecord<K, V>, Record.ReadonlyRecord<K, V>],
481
488
  );
482
489
  });
483
490