@typed/fx 2.0.0-beta.0 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/Fx/combinators/additive.d.ts +94 -0
- package/dist/Fx/combinators/additive.d.ts.map +1 -0
- package/dist/Fx/combinators/additive.js +92 -0
- package/dist/Fx/combinators/catch.d.ts +61 -0
- package/dist/Fx/combinators/catch.d.ts.map +1 -1
- package/dist/Fx/combinators/catch.js +54 -0
- package/dist/Fx/combinators/changesWithEffect.d.ts +20 -0
- package/dist/Fx/combinators/changesWithEffect.d.ts.map +1 -0
- package/dist/Fx/combinators/changesWithEffect.js +28 -0
- package/dist/Fx/combinators/dropUntil.d.ts +29 -0
- package/dist/Fx/combinators/dropUntil.d.ts.map +1 -0
- package/dist/Fx/combinators/dropUntil.js +23 -0
- package/dist/Fx/combinators/flatMapConcurrently.d.ts.map +1 -1
- package/dist/Fx/combinators/flatMapConcurrently.js +3 -2
- package/dist/Fx/combinators/index.d.ts +10 -0
- package/dist/Fx/combinators/index.d.ts.map +1 -1
- package/dist/Fx/combinators/index.js +10 -0
- package/dist/Fx/combinators/keyed.d.ts +1 -1
- package/dist/Fx/combinators/keyed.d.ts.map +1 -1
- package/dist/Fx/combinators/mapBoth.d.ts +21 -0
- package/dist/Fx/combinators/mapBoth.d.ts.map +1 -0
- package/dist/Fx/combinators/mapBoth.js +14 -0
- package/dist/Fx/combinators/mapError.d.ts +17 -0
- package/dist/Fx/combinators/mapError.d.ts.map +1 -0
- package/dist/Fx/combinators/mapError.js +16 -0
- package/dist/Fx/combinators/provide.d.ts +34 -1
- package/dist/Fx/combinators/provide.d.ts.map +1 -1
- package/dist/Fx/combinators/provide.js +27 -0
- package/dist/Fx/combinators/result.d.ts +23 -0
- package/dist/Fx/combinators/result.d.ts.map +1 -0
- package/dist/Fx/combinators/result.js +32 -0
- package/dist/Fx/combinators/scan.d.ts +33 -0
- package/dist/Fx/combinators/scan.d.ts.map +1 -0
- package/dist/Fx/combinators/scan.js +38 -0
- package/dist/Fx/combinators/skip.d.ts +13 -0
- package/dist/Fx/combinators/skip.d.ts.map +1 -1
- package/dist/Fx/combinators/skip.js +11 -0
- package/dist/Fx/combinators/skipWhile.d.ts +49 -0
- package/dist/Fx/combinators/skipWhile.d.ts.map +1 -0
- package/dist/Fx/combinators/skipWhile.js +66 -0
- package/dist/Fx/combinators/slice.d.ts +13 -0
- package/dist/Fx/combinators/slice.d.ts.map +1 -1
- package/dist/Fx/combinators/slice.js +11 -0
- package/dist/Fx/combinators/take.d.ts +13 -0
- package/dist/Fx/combinators/take.d.ts.map +1 -1
- package/dist/Fx/combinators/take.js +11 -0
- package/dist/Fx/combinators/takeUntil.d.ts +14 -0
- package/dist/Fx/combinators/takeUntil.d.ts.map +1 -1
- package/dist/Fx/combinators/takeUntil.js +14 -0
- package/dist/Fx/combinators/takeWhile.d.ts +29 -0
- package/dist/Fx/combinators/takeWhile.d.ts.map +1 -0
- package/dist/Fx/combinators/takeWhile.js +23 -0
- package/dist/Fx/combinators/zip.d.ts +75 -0
- package/dist/Fx/combinators/zip.d.ts.map +1 -0
- package/dist/Fx/combinators/zip.js +100 -0
- package/dist/Fx/constructors/at.d.ts +2 -2
- package/dist/Fx/constructors/at.d.ts.map +1 -1
- package/dist/Fx/constructors/periodic.d.ts +1 -1
- package/dist/Fx/constructors/periodic.d.ts.map +1 -1
- package/dist/Push/Push.d.ts +64 -1
- package/dist/Push/Push.d.ts.map +1 -1
- package/dist/Push/Push.js +57 -0
- package/dist/RefSubject/RefArray.d.ts.map +1 -1
- package/dist/RefSubject/RefArray.js +2 -1
- package/dist/RefSubject/RefChunk.d.ts.map +1 -1
- package/dist/RefSubject/RefChunk.js +2 -1
- package/dist/RefSubject/RefDateTime.d.ts +4 -4
- package/dist/RefSubject/RefDateTime.d.ts.map +1 -1
- package/dist/RefSubject/RefHashMap.d.ts.map +1 -1
- package/dist/RefSubject/RefHashMap.js +5 -1
- package/dist/RefSubject/RefIterable.d.ts +1 -1
- package/dist/RefSubject/RefIterable.d.ts.map +1 -1
- package/dist/RefSubject/RefIterable.js +6 -1
- package/dist/RefSubject/RefRecord.d.ts.map +1 -1
- package/dist/RefSubject/RefRecord.js +3 -2
- package/dist/RefSubject/RefSubject.d.ts +48 -1
- package/dist/RefSubject/RefSubject.d.ts.map +1 -1
- package/dist/RefSubject/RefSubject.js +80 -1
- package/dist/RefSubject/RefTrie.d.ts +7 -7
- package/dist/RefSubject/RefTrie.d.ts.map +1 -1
- package/dist/RefSubject/RefTrie.js +8 -3
- package/dist/Sink/combinators.d.ts +57 -0
- package/dist/Sink/combinators.d.ts.map +1 -1
- package/dist/Sink/combinators.js +104 -1
- package/dist/Versioned/Versioned.d.ts +30 -0
- package/dist/Versioned/Versioned.d.ts.map +1 -1
- package/dist/Versioned/Versioned.js +18 -0
- package/package.json +10 -6
- package/src/Fx/combinators/additive.ts +142 -0
- package/src/Fx/combinators/catch.ts +256 -0
- package/src/Fx/combinators/changesWithEffect.ts +66 -0
- package/src/Fx/combinators/dropUntil.ts +47 -0
- package/src/Fx/combinators/flatMapConcurrently.ts +5 -2
- package/src/Fx/combinators/index.ts +10 -0
- package/src/Fx/combinators/keyed.ts +2 -2
- package/src/Fx/combinators/mapBoth.ts +40 -0
- package/src/Fx/combinators/mapError.ts +28 -0
- package/src/Fx/combinators/provide.ts +63 -1
- package/src/Fx/combinators/result.ts +39 -0
- package/src/Fx/combinators/scan.ts +82 -0
- package/src/Fx/combinators/skip.ts +21 -0
- package/src/Fx/combinators/skipWhile.ts +100 -0
- package/src/Fx/combinators/slice.ts +23 -0
- package/src/Fx/combinators/take.ts +21 -0
- package/src/Fx/combinators/takeUntil.ts +38 -0
- package/src/Fx/combinators/takeWhile.ts +47 -0
- package/src/Fx/combinators/zip.ts +175 -0
- package/src/Fx/constructors/at.ts +3 -3
- package/src/Fx/constructors/periodic.ts +1 -1
- package/src/Fx.additive-combinators.test.ts +126 -0
- package/src/Fx.catch-additive.test.ts +206 -0
- package/src/Fx.catch.test.ts +1 -2
- package/src/Fx.dropUntil.test.ts +61 -0
- package/src/Fx.lifecycle.test.ts +1 -2
- package/src/Fx.mapError-mapBoth.test.ts +101 -0
- package/src/Fx.provide-combinators.test.ts +94 -0
- package/src/Fx.result-changesWithEffect.test.ts +112 -0
- package/src/Fx.scan.test.ts +73 -0
- package/src/Fx.takeWhile-skipWhile.test.ts +84 -0
- package/src/Fx.zip-merge-additive.test.ts +171 -0
- package/src/Fx.zip.test.ts +133 -0
- package/src/Push/Push.ts +170 -1
- package/src/Push.additive.test.ts +256 -0
- package/src/RefSubject/RefArray.ts +4 -1
- package/src/RefSubject/RefChunk.ts +2 -1
- package/src/RefSubject/RefDateTime.ts +6 -6
- package/src/RefSubject/RefHashMap.ts +10 -1
- package/src/RefSubject/RefIterable.ts +11 -2
- package/src/RefSubject/RefRecord.ts +9 -2
- package/src/RefSubject/RefSubject.ts +108 -9
- package/src/RefSubject/RefTrie.ts +19 -10
- package/src/RefSubject.additive-parity.test.ts +101 -0
- package/src/Sink/combinators.ts +123 -1
- package/src/Sink.combinators.test.ts +88 -0
- package/src/Sink.reduce-collect-head-last.test.ts +107 -0
- package/src/Versioned/Versioned.ts +76 -0
- package/src/Versioned.filterMap.test.ts +91 -0
- package/tsconfig.json +0 -6
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as Array from "effect/Array";
|
|
5
5
|
import type * as Cause from "effect/Cause";
|
|
6
6
|
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Semaphore from "effect/Semaphore";
|
|
7
8
|
import { equals } from "effect/Equal";
|
|
8
9
|
import type { Equivalence } from "effect/Equivalence";
|
|
9
10
|
import * as Exit from "effect/Exit";
|
|
@@ -459,14 +460,14 @@ class RefSubjectCore<A, E, R, R2> {
|
|
|
459
460
|
readonly services: ServiceMap.ServiceMap<R2>;
|
|
460
461
|
readonly scope: Scope.Closeable;
|
|
461
462
|
readonly deferredRef: DeferredRef.DeferredRef<E, A>;
|
|
462
|
-
readonly semaphore:
|
|
463
|
+
readonly semaphore: Semaphore.Semaphore;
|
|
463
464
|
constructor(
|
|
464
465
|
initial: Effect.Effect<A, E, R>,
|
|
465
466
|
subject: Subject.HoldSubjectImpl<A, E>,
|
|
466
467
|
services: ServiceMap.ServiceMap<R2>,
|
|
467
468
|
scope: Scope.Closeable,
|
|
468
469
|
deferredRef: DeferredRef.DeferredRef<E, A>,
|
|
469
|
-
semaphore:
|
|
470
|
+
semaphore: Semaphore.Semaphore,
|
|
470
471
|
) {
|
|
471
472
|
this.initial = initial;
|
|
472
473
|
this.subject = subject;
|
|
@@ -693,6 +694,66 @@ export function fromStream<A, E, R>(
|
|
|
693
694
|
});
|
|
694
695
|
}
|
|
695
696
|
|
|
697
|
+
/**
|
|
698
|
+
* Creates a `RefSubject` from an `Option` value.
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* import { Effect, Option } from "effect"
|
|
703
|
+
* import * as RefSubject from "@typed/fx/RefSubject"
|
|
704
|
+
*
|
|
705
|
+
* const program = Effect.gen(function* () {
|
|
706
|
+
* const ref = yield* RefSubject.fromOption(Option.some(42))
|
|
707
|
+
* const value = yield* ref
|
|
708
|
+
* console.log(Option.isSome(value)) // true
|
|
709
|
+
* })
|
|
710
|
+
* ```
|
|
711
|
+
*
|
|
712
|
+
* @since 1.0.0
|
|
713
|
+
* @category constructors
|
|
714
|
+
*/
|
|
715
|
+
export function fromOption<A>(
|
|
716
|
+
option: Option.Option<A>,
|
|
717
|
+
options?: RefSubjectOptions<Option.Option<A>>,
|
|
718
|
+
): Effect.Effect<RefSubject<Option.Option<A>>, never, Scope.Scope> {
|
|
719
|
+
return make(option, options);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Creates a `RefSubject` from a nullable value (null/undefined become `Option.none()`).
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```ts
|
|
727
|
+
* import { Effect, Option } from "effect"
|
|
728
|
+
* import * as RefSubject from "@typed/fx/RefSubject"
|
|
729
|
+
*
|
|
730
|
+
* const program = Effect.gen(function* () {
|
|
731
|
+
* const ref = yield* RefSubject.fromNullable("hello")
|
|
732
|
+
* const value = yield* ref
|
|
733
|
+
* console.log(Option.isSome(value)) // true
|
|
734
|
+
*
|
|
735
|
+
* const empty = yield* RefSubject.fromNullable(null)
|
|
736
|
+
* const none = yield* empty
|
|
737
|
+
* console.log(Option.isNone(none)) // true
|
|
738
|
+
* })
|
|
739
|
+
* ```
|
|
740
|
+
*
|
|
741
|
+
* @since 1.0.0
|
|
742
|
+
* @category constructors
|
|
743
|
+
*/
|
|
744
|
+
function optionFromNullable<A>(value: A | null | undefined): Option.Option<NonNullable<A>> {
|
|
745
|
+
return value === null || value === undefined
|
|
746
|
+
? Option.none()
|
|
747
|
+
: Option.some(value as NonNullable<A>);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export function fromNullable<A>(
|
|
751
|
+
value: A | null | undefined,
|
|
752
|
+
options?: RefSubjectOptions<Option.Option<NonNullable<A>>>,
|
|
753
|
+
): Effect.Effect<RefSubject<Option.Option<NonNullable<A>>>, never, Scope.Scope> {
|
|
754
|
+
return make(optionFromNullable(value), options);
|
|
755
|
+
}
|
|
756
|
+
|
|
696
757
|
function redirectCause<A, E, R>(core: RefSubjectCore<A, E, R, R | Scope.Scope>) {
|
|
697
758
|
return Stream.catchCause((cause: Cause.Cause<E>) =>
|
|
698
759
|
Stream.unwrap(Effect.as(onFailureCore(core, cause), Stream.empty)),
|
|
@@ -716,7 +777,7 @@ function makeCore<A, E, R>(
|
|
|
716
777
|
scope,
|
|
717
778
|
deferredRef ??
|
|
718
779
|
DeferredRef.unsafeMake(id, getExitEquivalence(options?.eq ?? equals), subject.lastValue),
|
|
719
|
-
|
|
780
|
+
Semaphore.makeUnsafe(1),
|
|
720
781
|
);
|
|
721
782
|
yield* Scope.addFinalizer(scope, core.subject.interrupt);
|
|
722
783
|
return core;
|
|
@@ -1179,12 +1240,10 @@ export const runUpdates: {
|
|
|
1179
1240
|
<A, E, R, B, E2, R2, R3 = never, E3 = never, C = never>(
|
|
1180
1241
|
ref: RefSubject<A, E, R>,
|
|
1181
1242
|
f: (ref: GetSetDelete<A, E, R>) => Effect.Effect<B, E2, R2>,
|
|
1182
|
-
options?:
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
| undefined,
|
|
1243
|
+
options?: {
|
|
1244
|
+
readonly onInterrupt: (value: A) => Effect.Effect<C, E3, R3>;
|
|
1245
|
+
readonly value?: "initial" | "current";
|
|
1246
|
+
},
|
|
1188
1247
|
): Effect.Effect<B, E | E2 | E3, R | R2 | R3>;
|
|
1189
1248
|
} = dual(
|
|
1190
1249
|
isRefSubjectDataFirst,
|
|
@@ -1741,6 +1800,46 @@ export const compact: {
|
|
|
1741
1800
|
return new FilteredImpl(versioned, Effect.succeed);
|
|
1742
1801
|
};
|
|
1743
1802
|
|
|
1803
|
+
/**
|
|
1804
|
+
* Returns a `Computed` that yields the value inside the `Option`, or the fallback when `None`.
|
|
1805
|
+
* Works with `Computed<Option<A>>` (e.g. from `fromOption` / `fromNullable`) and with `Filtered<A>`.
|
|
1806
|
+
*
|
|
1807
|
+
* @example
|
|
1808
|
+
* ```ts
|
|
1809
|
+
* import { Effect, Option } from "effect"
|
|
1810
|
+
* import * as RefSubject from "@typed/fx/RefSubject"
|
|
1811
|
+
*
|
|
1812
|
+
* const program = Effect.gen(function* () {
|
|
1813
|
+
* const ref = yield* RefSubject.fromOption(Option.some(42))
|
|
1814
|
+
* const withDefault = RefSubject.getOrElse(ref, () => 0)
|
|
1815
|
+
* expect(yield* withDefault).toBe(42)
|
|
1816
|
+
*
|
|
1817
|
+
* const empty = yield* RefSubject.fromNullable(null)
|
|
1818
|
+
* const fallback = RefSubject.getOrElse(empty, () => 99)
|
|
1819
|
+
* expect(yield* fallback).toBe(99)
|
|
1820
|
+
* })
|
|
1821
|
+
* ```
|
|
1822
|
+
*
|
|
1823
|
+
* @since 1.0.0
|
|
1824
|
+
* @category combinators
|
|
1825
|
+
*/
|
|
1826
|
+
export const getOrElse: {
|
|
1827
|
+
<A>(
|
|
1828
|
+
fallback: () => A,
|
|
1829
|
+
): <E, R>(ref: Computed<Option.Option<A>, E, R> | Filtered<A, E, R>) => Computed<A, E, R>;
|
|
1830
|
+
<A, E, R>(
|
|
1831
|
+
ref: Computed<Option.Option<A>, E, R> | Filtered<A, E, R>,
|
|
1832
|
+
fallback: () => A,
|
|
1833
|
+
): Computed<A, E, R>;
|
|
1834
|
+
} = dual(2, function getOrElse<
|
|
1835
|
+
A,
|
|
1836
|
+
E,
|
|
1837
|
+
R,
|
|
1838
|
+
>(ref: Computed<Option.Option<A>, E, R> | Filtered<A, E, R>, fallback: () => A): Computed<A, E, R> {
|
|
1839
|
+
const computed = FilteredTypeId in ref ? (ref as Filtered<A, E, R>).asComputed() : ref;
|
|
1840
|
+
return map(computed, (opt) => Option.getOrElse(opt, fallback));
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1744
1843
|
class RefSubjectSimpleTransform<A, E, R, R2, R3>
|
|
1745
1844
|
extends YieldableFx<A, E, R | R2 | Scope.Scope, A, E, R | R3>
|
|
1746
1845
|
implements RefSubject<A, E, R | R2 | R3>
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
import type * as Effect from "effect/Effect";
|
|
7
7
|
import { equals } from "effect/Equal";
|
|
8
8
|
import { dual } from "effect/Function";
|
|
9
|
-
import
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
10
|
import type * as Scope from "effect/Scope";
|
|
11
11
|
import * as Trie from "effect/Trie";
|
|
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 RefTrie is a RefSubject specialized over a Trie.
|
|
@@ -122,13 +123,13 @@ export const clear = <V, E, R>(ref: RefTrie<V, E, R>): Effect.Effect<Trie.Trie<V
|
|
|
122
123
|
export const map: {
|
|
123
124
|
<V>(
|
|
124
125
|
f: (value: V, key: string) => V,
|
|
125
|
-
): <E, R>(ref: RefTrie<V, E, R>) =>
|
|
126
|
+
): <E, R>(ref: RefTrie<V, E, R>) => RefSubject.Computed<Trie.Trie<V>, E, R>;
|
|
126
127
|
<V, E, R>(
|
|
127
128
|
ref: RefTrie<V, E, R>,
|
|
128
129
|
f: (value: V, key: string) => V,
|
|
129
|
-
):
|
|
130
|
+
): RefSubject.Computed<Trie.Trie<V>, E, R>;
|
|
130
131
|
} = dual(2, function map<V, E, R>(ref: RefTrie<V, E, R>, f: (value: V, key: string) => V) {
|
|
131
|
-
return RefSubject.
|
|
132
|
+
return RefSubject.map(ref, Trie.map(f));
|
|
132
133
|
});
|
|
133
134
|
|
|
134
135
|
/**
|
|
@@ -139,17 +140,17 @@ export const map: {
|
|
|
139
140
|
export const filter: {
|
|
140
141
|
<V>(
|
|
141
142
|
predicate: (value: V, key: string) => boolean,
|
|
142
|
-
): <E, R>(ref: RefTrie<V, E, R>) =>
|
|
143
|
+
): <E, R>(ref: RefTrie<V, E, R>) => RefSubject.Computed<Trie.Trie<V>, E, R>;
|
|
143
144
|
<V, E, R>(
|
|
144
145
|
ref: RefTrie<V, E, R>,
|
|
145
146
|
predicate: (value: V, key: string) => boolean,
|
|
146
|
-
):
|
|
147
|
+
): RefSubject.Computed<Trie.Trie<V>, E, R>;
|
|
147
148
|
} = dual(2, function filter<
|
|
148
149
|
V,
|
|
149
150
|
E,
|
|
150
151
|
R,
|
|
151
152
|
>(ref: RefTrie<V, E, R>, predicate: (value: V, key: string) => boolean) {
|
|
152
|
-
return RefSubject.
|
|
153
|
+
return RefSubject.map(ref, Trie.filter(predicate));
|
|
153
154
|
});
|
|
154
155
|
|
|
155
156
|
/**
|
|
@@ -160,17 +161,25 @@ export const filter: {
|
|
|
160
161
|
export const filterMap: {
|
|
161
162
|
<V>(
|
|
162
163
|
f: (value: V, key: string) => Option.Option<V>,
|
|
163
|
-
): <E, R>(ref: RefTrie<V, E, R>) =>
|
|
164
|
+
): <E, R>(ref: RefTrie<V, E, R>) => RefSubject.Computed<Trie.Trie<V>, E, R>;
|
|
164
165
|
<V, E, R>(
|
|
165
166
|
ref: RefTrie<V, E, R>,
|
|
166
167
|
f: (value: V, key: string) => Option.Option<V>,
|
|
167
|
-
):
|
|
168
|
+
): RefSubject.Computed<Trie.Trie<V>, E, R>;
|
|
168
169
|
} = dual(2, function filterMap<
|
|
169
170
|
V,
|
|
170
171
|
E,
|
|
171
172
|
R,
|
|
172
173
|
>(ref: RefTrie<V, E, R>, f: (value: V, key: string) => Option.Option<V>) {
|
|
173
|
-
return RefSubject.
|
|
174
|
+
return RefSubject.map(
|
|
175
|
+
ref,
|
|
176
|
+
Trie.filterMap((value, key) =>
|
|
177
|
+
Option.match(f(value, key), {
|
|
178
|
+
onNone: () => Result.failVoid,
|
|
179
|
+
onSome: (b) => Result.succeed(b),
|
|
180
|
+
}),
|
|
181
|
+
),
|
|
182
|
+
);
|
|
174
183
|
});
|
|
175
184
|
|
|
176
185
|
// ========================================
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import { RefSubject } from "./index.js";
|
|
5
|
+
|
|
6
|
+
const fromNullableOption = <A>(v: A | null | undefined): Option.Option<NonNullable<A>> =>
|
|
7
|
+
v === null || v === undefined ? Option.none() : Option.some(v as NonNullable<A>);
|
|
8
|
+
|
|
9
|
+
describe("RefSubject additive parity (fromOption, fromNullable, getOrElse)", () => {
|
|
10
|
+
describe("fromOption", () => {
|
|
11
|
+
it("creates RefSubject from Option.some", () =>
|
|
12
|
+
Effect.gen(function* () {
|
|
13
|
+
const ref = yield* RefSubject.fromOption(Option.some(42));
|
|
14
|
+
const value = yield* ref;
|
|
15
|
+
expect(Option.isSome(value)).toBe(true);
|
|
16
|
+
if (Option.isSome(value)) {
|
|
17
|
+
expect(value.value).toBe(42);
|
|
18
|
+
}
|
|
19
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
20
|
+
|
|
21
|
+
it("creates RefSubject from Option.none", () =>
|
|
22
|
+
Effect.gen(function* () {
|
|
23
|
+
const ref = yield* RefSubject.fromOption(Option.none<number>());
|
|
24
|
+
const value = yield* ref;
|
|
25
|
+
expect(Option.isNone(value)).toBe(true);
|
|
26
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
27
|
+
|
|
28
|
+
it("is writable (set/update)", () =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const ref = yield* RefSubject.fromOption(Option.some(1));
|
|
31
|
+
yield* RefSubject.set(ref, Option.some(2));
|
|
32
|
+
const value = yield* ref;
|
|
33
|
+
expect(Option.isSome(value)).toBe(true);
|
|
34
|
+
if (Option.isSome(value)) {
|
|
35
|
+
expect(value.value).toBe(2);
|
|
36
|
+
}
|
|
37
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("fromNullable", () => {
|
|
41
|
+
it("creates RefSubject from non-null value", () =>
|
|
42
|
+
Effect.gen(function* () {
|
|
43
|
+
const ref = yield* RefSubject.fromNullable("hello");
|
|
44
|
+
const value = yield* ref;
|
|
45
|
+
expect(Option.isSome(value)).toBe(true);
|
|
46
|
+
if (Option.isSome(value)) {
|
|
47
|
+
expect(value.value).toBe("hello");
|
|
48
|
+
}
|
|
49
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
50
|
+
|
|
51
|
+
it("creates RefSubject from null", () =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const ref = yield* RefSubject.fromNullable<string>(null);
|
|
54
|
+
const value = yield* ref;
|
|
55
|
+
expect(Option.isNone(value)).toBe(true);
|
|
56
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
57
|
+
|
|
58
|
+
it("creates RefSubject from undefined", () =>
|
|
59
|
+
Effect.gen(function* () {
|
|
60
|
+
const ref = yield* RefSubject.fromNullable<number>(undefined);
|
|
61
|
+
const value = yield* ref;
|
|
62
|
+
expect(Option.isNone(value)).toBe(true);
|
|
63
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("getOrElse", () => {
|
|
67
|
+
it("returns value for Computed<Option<A>> when Some", () =>
|
|
68
|
+
Effect.gen(function* () {
|
|
69
|
+
const ref = yield* RefSubject.fromOption(Option.some(42));
|
|
70
|
+
const withDefault = RefSubject.getOrElse(ref, () => 0);
|
|
71
|
+
expect(yield* withDefault).toBe(42);
|
|
72
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
73
|
+
|
|
74
|
+
it("returns fallback for Computed<Option<A>> when None", () =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
const ref = yield* RefSubject.fromNullable<number>(null);
|
|
77
|
+
const withDefault = RefSubject.getOrElse(ref, () => 99);
|
|
78
|
+
expect(yield* withDefault).toBe(99);
|
|
79
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
80
|
+
|
|
81
|
+
it("getOrElse on Filtered uses fallback when no value", () =>
|
|
82
|
+
Effect.gen(function* () {
|
|
83
|
+
const numbers = yield* RefSubject.make([1, 3, 5]);
|
|
84
|
+
const firstEven = RefSubject.filterMap(numbers, (arr) =>
|
|
85
|
+
fromNullableOption(arr.find((n) => n % 2 === 0)),
|
|
86
|
+
);
|
|
87
|
+
const withDefault = RefSubject.getOrElse(firstEven, () => -1);
|
|
88
|
+
expect(yield* withDefault).toBe(-1);
|
|
89
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
90
|
+
|
|
91
|
+
it("getOrElse on Filtered returns value when present", () =>
|
|
92
|
+
Effect.gen(function* () {
|
|
93
|
+
const numbers = yield* RefSubject.make([1, 2, 3]);
|
|
94
|
+
const firstEven = RefSubject.filterMap(numbers, (arr) =>
|
|
95
|
+
fromNullableOption(arr.find((n) => n % 2 === 0)),
|
|
96
|
+
);
|
|
97
|
+
const withDefault = RefSubject.getOrElse(firstEven, () => -1);
|
|
98
|
+
expect(yield* withDefault).toBe(2);
|
|
99
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
100
|
+
});
|
|
101
|
+
});
|
package/src/Sink/combinators.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Cause from "effect/Cause";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Semaphore from "effect/Semaphore";
|
|
3
4
|
import * as Exit from "effect/Exit";
|
|
4
5
|
import { dual, flow, identity } from "effect/Function";
|
|
5
6
|
import * as MutableRef from "effect/MutableRef";
|
|
@@ -72,6 +73,40 @@ export function map<A, E, R, B>(sink: Sink<A, E, R>, f: (b: B) => A): Sink<B, E,
|
|
|
72
73
|
return MapSink.make(sink, f);
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/** Alias for `map`: transforms input values before they reach the sink. @since 1.0.0 @category combinators */
|
|
77
|
+
export const mapInput = map;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Maps the error channel of a sink using the provided function.
|
|
81
|
+
* Failures are mapped via `Cause.map`; defects and interrupts are preserved.
|
|
82
|
+
*
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
* @category combinators
|
|
85
|
+
*/
|
|
86
|
+
export function mapError<A, E, E2, R>(sink: Sink<A, E2, R>, f: (e: E) => E2): Sink<A, E, R> {
|
|
87
|
+
return new MapErrorSink(sink, f);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class MapErrorSink<A, E, E2, R> implements Sink<A, E, R> {
|
|
91
|
+
readonly sink: Sink<A, E2, R>;
|
|
92
|
+
readonly f: (e: E) => E2;
|
|
93
|
+
|
|
94
|
+
constructor(sink: Sink<A, E2, R>, f: (e: E) => E2) {
|
|
95
|
+
this.sink = sink;
|
|
96
|
+
this.f = f;
|
|
97
|
+
this.onSuccess = this.onSuccess.bind(this);
|
|
98
|
+
this.onFailure = this.onFailure.bind(this);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
onSuccess(value: A): Effect.Effect<unknown, never, R> {
|
|
102
|
+
return this.sink.onSuccess(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onFailure(cause: Cause.Cause<E>): Effect.Effect<unknown, never, R> {
|
|
106
|
+
return this.sink.onFailure(Cause.map(cause, this.f));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
75
110
|
class FilterMapSink<A, E, R, B> implements Sink<B, E, R> {
|
|
76
111
|
readonly sink: Sink<A, E, R>;
|
|
77
112
|
readonly f: (b: B) => Option.Option<A>;
|
|
@@ -236,7 +271,7 @@ export function withStateSemaphore<A, E, R, B, R2>(
|
|
|
236
271
|
) {
|
|
237
272
|
return withEarlyExit(sink, (sink, params) => {
|
|
238
273
|
const stateRef = MutableRef.make(state);
|
|
239
|
-
const semaphore =
|
|
274
|
+
const semaphore = Semaphore.makeUnsafe(1);
|
|
240
275
|
const lock = semaphore.withPermits(1);
|
|
241
276
|
const modifyEffect = <C, E2, R2>(f: (state: B) => Effect.Effect<readonly [C, B], E2, R2>) =>
|
|
242
277
|
Effect.suspend(() => f(MutableRef.get(stateRef))).pipe(
|
|
@@ -776,6 +811,9 @@ class MapEffectSink<A, E, R, B, E2, R2> implements Sink<B, E | E2, R | R2> {
|
|
|
776
811
|
}
|
|
777
812
|
}
|
|
778
813
|
|
|
814
|
+
/** Alias for `mapEffect`: transforms input with an effect before the sink. @since 1.0.0 @category combinators */
|
|
815
|
+
export const mapInputEffect = mapEffect;
|
|
816
|
+
|
|
779
817
|
export const filterMapEffect: {
|
|
780
818
|
<B, A, E2, R2>(
|
|
781
819
|
f: (b: B) => Effect.Effect<Option.Option<A>, E2, R2>,
|
|
@@ -991,3 +1029,87 @@ export const skipInterrupt = <A, E, R>(sink: Sink<A, E, R>): Sink<A, E, R> => {
|
|
|
991
1029
|
cause.reasons.every(Cause.isInterruptReason) ? Effect.void : sink.onFailure(cause),
|
|
992
1030
|
};
|
|
993
1031
|
};
|
|
1032
|
+
|
|
1033
|
+
// -----------------------------------------------------------------------------
|
|
1034
|
+
// Reducing / collecting combinators (additive)
|
|
1035
|
+
// -----------------------------------------------------------------------------
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Reduces values into a single result using a pure function. Pass a `Ref<B>`
|
|
1039
|
+
* (e.g. from `Ref.make(initial)`); after running, read the result with `Ref.get(ref)`.
|
|
1040
|
+
*
|
|
1041
|
+
* @since 1.0.0
|
|
1042
|
+
* @category combinators
|
|
1043
|
+
*/
|
|
1044
|
+
export function reduce<A, B, E>(ref: Ref.Ref<B>, f: (b: B, a: A) => B): Sink<A, E, never> {
|
|
1045
|
+
return {
|
|
1046
|
+
onSuccess: (value) => Ref.update(ref, (b) => f(b, value)),
|
|
1047
|
+
onFailure: () => Effect.void,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Reduces values into a single result using an effectful function. Pass a `Ref<B>`;
|
|
1053
|
+
* after running, read the result with `Ref.get(ref)`. If the reducer effect fails,
|
|
1054
|
+
* the ref is left unchanged (Sink onSuccess is typed as never failing).
|
|
1055
|
+
*
|
|
1056
|
+
* @since 1.0.0
|
|
1057
|
+
* @category combinators
|
|
1058
|
+
*/
|
|
1059
|
+
export function reduceEffect<A, B, E, E2, R2>(
|
|
1060
|
+
ref: Ref.Ref<B>,
|
|
1061
|
+
f: (b: B, a: A) => Effect.Effect<B, E2, R2>,
|
|
1062
|
+
): Sink<A, E | E2, R2> {
|
|
1063
|
+
return {
|
|
1064
|
+
onSuccess: (value) =>
|
|
1065
|
+
Effect.flatMap(Ref.get(ref), (b) =>
|
|
1066
|
+
Effect.matchCauseEffect(f(b, value), {
|
|
1067
|
+
onFailure: () => Effect.void,
|
|
1068
|
+
onSuccess: (next) => Ref.set(ref, next),
|
|
1069
|
+
}),
|
|
1070
|
+
),
|
|
1071
|
+
onFailure: () => Effect.void,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Collects all values into an array. Pass a `Ref<ReadonlyArray<A>>` (e.g. `Ref.make([])`);
|
|
1077
|
+
* after running, read the result with `Ref.get(ref)`.
|
|
1078
|
+
*
|
|
1079
|
+
* @since 1.0.0
|
|
1080
|
+
* @category combinators
|
|
1081
|
+
*/
|
|
1082
|
+
export function collect<A, E>(ref: Ref.Ref<ReadonlyArray<A>>): Sink<A, E, never> {
|
|
1083
|
+
return {
|
|
1084
|
+
onSuccess: (value) => Ref.update(ref, (arr) => [...arr, value]),
|
|
1085
|
+
onFailure: () => Effect.void,
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Keeps only the first value. Pass a `Ref<Option.Option<A>>` (e.g. `Ref.make(Option.none())`);
|
|
1091
|
+
* after running, read the result with `Ref.get(ref)`.
|
|
1092
|
+
*
|
|
1093
|
+
* @since 1.0.0
|
|
1094
|
+
* @category combinators
|
|
1095
|
+
*/
|
|
1096
|
+
export function head<A, E>(ref: Ref.Ref<Option.Option<A>>): Sink<A, E, never> {
|
|
1097
|
+
return {
|
|
1098
|
+
onSuccess: (value) => Ref.update(ref, (opt) => (Option.isNone(opt) ? Option.some(value) : opt)),
|
|
1099
|
+
onFailure: () => Effect.void,
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Keeps only the last value. Pass a `Ref<Option.Option<A>>` (e.g. `Ref.make(Option.none())`);
|
|
1105
|
+
* after running, read the result with `Ref.get(ref)`.
|
|
1106
|
+
*
|
|
1107
|
+
* @since 1.0.0
|
|
1108
|
+
* @category combinators
|
|
1109
|
+
*/
|
|
1110
|
+
export function last<A, E>(ref: Ref.Ref<Option.Option<A>>): Sink<A, E, never> {
|
|
1111
|
+
return {
|
|
1112
|
+
onSuccess: (value) => Ref.set(ref, Option.some(value)),
|
|
1113
|
+
onFailure: () => Effect.void,
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { assert, describe, it } from "vitest";
|
|
2
|
+
import * as Cause from "effect/Cause";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Ref from "effect/Ref";
|
|
5
|
+
import { Fx, Sink } from "./index.js";
|
|
6
|
+
|
|
7
|
+
describe("Sink.mapError", () => {
|
|
8
|
+
it("maps failure cause before passing to inner sink", () =>
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const failures = yield* Ref.make<Array<number>>([]);
|
|
11
|
+
const sink = Sink.make<unknown, number, never>(
|
|
12
|
+
(cause) =>
|
|
13
|
+
Ref.update(failures, (list) => {
|
|
14
|
+
const f = Cause.findFail(cause);
|
|
15
|
+
if (f._tag === "Success") list.push(f.success.error as number);
|
|
16
|
+
return list;
|
|
17
|
+
}),
|
|
18
|
+
() => Effect.void,
|
|
19
|
+
);
|
|
20
|
+
const mapped = Sink.mapError(sink, (s: string) => s.length);
|
|
21
|
+
yield* Fx.fail("err").run(mapped);
|
|
22
|
+
const list = yield* Ref.get(failures);
|
|
23
|
+
assert.deepStrictEqual(list, [3]);
|
|
24
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
25
|
+
|
|
26
|
+
it("preserves success path", () =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const out = yield* Ref.make<number[]>([]);
|
|
29
|
+
const sink = Sink.make<number, string, never>(
|
|
30
|
+
(_cause) => Effect.void,
|
|
31
|
+
(n: number) => Ref.update(out, (arr) => [...arr, n]),
|
|
32
|
+
);
|
|
33
|
+
const mapped = Sink.mapError(sink, (e: string) => e);
|
|
34
|
+
yield* Fx.fromIterable([1, 2, 3]).run(mapped);
|
|
35
|
+
const list = yield* Ref.get(out);
|
|
36
|
+
assert.deepStrictEqual(list, [1, 2, 3]);
|
|
37
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("Sink.mapInput", () => {
|
|
41
|
+
it("transforms input before sink (same as map)", () =>
|
|
42
|
+
Effect.gen(function* () {
|
|
43
|
+
const out = yield* Ref.make<number[]>([]);
|
|
44
|
+
const sink = Sink.make(
|
|
45
|
+
() => Effect.void,
|
|
46
|
+
(n: number) => Ref.update(out, (arr) => [...arr, n]),
|
|
47
|
+
);
|
|
48
|
+
const mapped = Sink.mapInput(sink, (s: string) => parseInt(s, 10));
|
|
49
|
+
yield* Fx.fromIterable(["1", "2", "3"]).run(mapped);
|
|
50
|
+
const list = yield* Ref.get(out);
|
|
51
|
+
assert.deepStrictEqual(list, [1, 2, 3]);
|
|
52
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("Sink.mapInputEffect", () => {
|
|
56
|
+
it("transforms input with effect before sink (same as mapEffect)", () =>
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const out = yield* Ref.make<number[]>([]);
|
|
59
|
+
const sink = Sink.make(
|
|
60
|
+
() => Effect.void,
|
|
61
|
+
(n: number) => Ref.update(out, (arr) => [...arr, n]),
|
|
62
|
+
);
|
|
63
|
+
const mapped = Sink.mapInputEffect(sink, (s: string) => Effect.succeed(parseInt(s, 10)));
|
|
64
|
+
yield* Fx.fromIterable(["1", "2", "3"]).run(mapped);
|
|
65
|
+
const list = yield* Ref.get(out);
|
|
66
|
+
assert.deepStrictEqual(list, [1, 2, 3]);
|
|
67
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
68
|
+
|
|
69
|
+
it("propagates effect failure to sink onFailure", () =>
|
|
70
|
+
Effect.gen(function* () {
|
|
71
|
+
const failures = yield* Ref.make<Array<string>>([]);
|
|
72
|
+
const sink = Sink.make<number, string, never>(
|
|
73
|
+
(cause) =>
|
|
74
|
+
Ref.update(failures, (list) => {
|
|
75
|
+
const f = Cause.findFail(cause);
|
|
76
|
+
if (f._tag === "Success") list.push(f.success.error as string);
|
|
77
|
+
return list;
|
|
78
|
+
}),
|
|
79
|
+
(_n: number) => Effect.void,
|
|
80
|
+
);
|
|
81
|
+
const mapped = Sink.mapInputEffect(sink, (s: string) =>
|
|
82
|
+
s === "bad" ? Effect.fail("parse failed") : Effect.succeed(parseInt(s, 10)),
|
|
83
|
+
);
|
|
84
|
+
yield* Fx.fromIterable(["1", "bad", "3"]).run(mapped);
|
|
85
|
+
const list = yield* Ref.get(failures);
|
|
86
|
+
assert.deepStrictEqual(list, ["parse failed"]);
|
|
87
|
+
}).pipe(Effect.scoped, Effect.runPromise));
|
|
88
|
+
});
|