@typed/fx 1.1.1 → 1.2.0
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/dist/Subject/Computed.d.ts +12 -0
- package/dist/Subject/Computed.d.ts.map +1 -0
- package/dist/Subject/Computed.js +54 -0
- package/dist/Subject/Computed.js.map +1 -0
- package/dist/Subject/RefSubject.d.ts +11 -4
- package/dist/Subject/RefSubject.d.ts.map +1 -1
- package/dist/Subject/RefSubject.js +68 -24
- package/dist/Subject/RefSubject.js.map +1 -1
- package/dist/Subject/SynchronizedSubject.d.ts.map +1 -1
- package/dist/Subject/SynchronizedSubject.js +5 -6
- package/dist/Subject/SynchronizedSubject.js.map +1 -1
- package/dist/cjs/Subject/Computed.d.ts +12 -0
- package/dist/cjs/Subject/Computed.d.ts.map +1 -0
- package/dist/cjs/Subject/Computed.js +80 -0
- package/dist/cjs/Subject/Computed.js.map +1 -0
- package/dist/cjs/Subject/RefSubject.d.ts +11 -4
- package/dist/cjs/Subject/RefSubject.d.ts.map +1 -1
- package/dist/cjs/Subject/RefSubject.js +68 -24
- package/dist/cjs/Subject/RefSubject.js.map +1 -1
- package/dist/cjs/Subject/SynchronizedSubject.d.ts.map +1 -1
- package/dist/cjs/Subject/SynchronizedSubject.js +5 -6
- package/dist/cjs/Subject/SynchronizedSubject.js.map +1 -1
- package/dist/cjs/operator/multicast.d.ts +1 -1
- package/dist/cjs/operator/multicast.d.ts.map +1 -1
- package/dist/cjs/operator/multicast.js.map +1 -1
- package/dist/operator/multicast.d.ts +1 -1
- package/dist/operator/multicast.d.ts.map +1 -1
- package/dist/operator/multicast.js.map +1 -1
- package/dist/tsconfig.cjs.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/Subject/HoldSubject.ts +1 -6
- package/src/Subject/RefSubject.test.ts +81 -0
- package/src/Subject/RefSubject.ts +257 -37
- package/src/Subject/SynchronizedSubject.ts +8 -7
- package/src/operator/multicast.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -1483
- package/coverage/coverage-final.json +0 -40
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -191
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/Fx.ts.html +0 -274
- package/coverage/src/Subject/Subject.ts.html +0 -214
- package/coverage/src/Subject/index.html +0 -116
- package/coverage/src/_internal/RefCounter.ts.html +0 -313
- package/coverage/src/_internal/earlyExit.ts.html +0 -136
- package/coverage/src/_internal/index.html +0 -131
- package/coverage/src/constructor/at.ts.html +0 -118
- package/coverage/src/constructor/fromArray.ts.html +0 -148
- package/coverage/src/constructor/fromEffect.ts.html +0 -148
- package/coverage/src/constructor/fromEmitter.ts.html +0 -265
- package/coverage/src/constructor/fromIterable.ts.html +0 -172
- package/coverage/src/constructor/index.html +0 -236
- package/coverage/src/constructor/never.ts.html +0 -133
- package/coverage/src/constructor/serviceWith.ts.html +0 -122
- package/coverage/src/constructor/succeed.ts.html +0 -138
- package/coverage/src/constructor/suspend.ts.html +0 -130
- package/coverage/src/index.html +0 -116
- package/coverage/src/operator/concatMap.ts.html +0 -112
- package/coverage/src/operator/continueWith.ts.html +0 -169
- package/coverage/src/operator/delay.ts.html +0 -142
- package/coverage/src/operator/filter.ts.html +0 -190
- package/coverage/src/operator/filterEffect.ts.html +0 -214
- package/coverage/src/operator/filterMap.ts.html +0 -194
- package/coverage/src/operator/filterMapEffect.ts.html +0 -217
- package/coverage/src/operator/flatMap.ts.html +0 -211
- package/coverage/src/operator/flatMapConcurrently.ts.html +0 -141
- package/coverage/src/operator/index.html +0 -431
- package/coverage/src/operator/loop.ts.html +0 -214
- package/coverage/src/operator/map.ts.html +0 -163
- package/coverage/src/operator/merge.ts.html +0 -235
- package/coverage/src/operator/multicast.ts.html +0 -424
- package/coverage/src/operator/provideService.ts.html +0 -154
- package/coverage/src/operator/provideServiceEffect.ts.html +0 -190
- package/coverage/src/operator/scan.ts.html +0 -146
- package/coverage/src/operator/scanEffect.ts.html +0 -283
- package/coverage/src/operator/skipRepeats.ts.html +0 -214
- package/coverage/src/operator/slice.ts.html +0 -221
- package/coverage/src/operator/startWith.ts.html +0 -131
- package/coverage/src/operator/switchMap.ts.html +0 -295
- package/coverage/src/operator/withPermit.ts.html +0 -139
- package/coverage/src/run/collectAll.ts.html +0 -136
- package/coverage/src/run/index.html +0 -161
- package/coverage/src/run/observe.ts.html +0 -142
- package/coverage/src/run/reduce.ts.html +0 -149
- package/coverage/src/run/run.ts.html +0 -232
- package/coverage/tmp/coverage-13497-1671499520588-32.json +0 -1
- package/coverage/tmp/coverage-13497-1671499520647-31.json +0 -1
- package/coverage/tmp/coverage-13497-1671499520712-30.json +0 -1
- package/coverage/tmp/coverage-13497-1671499520782-26.json +0 -1
- package/coverage/tmp/coverage-13497-1671499520842-29.json +0 -1
- package/coverage/tmp/coverage-13497-1671499520906-28.json +0 -1
- package/coverage/tmp/coverage-13497-1671499520969-27.json +0 -1
- package/dist/constructor/fromFxEffectGen.d.ts +0 -5
- package/dist/constructor/fromFxEffectGen.d.ts.map +0 -1
- package/dist/constructor/fromFxEffectGen.js +0 -6
- package/dist/constructor/fromFxEffectGen.js.map +0 -1
- package/dist/constructor/genFx.d.ts +0 -5
- package/dist/constructor/genFx.d.ts.map +0 -1
- package/dist/constructor/genFx.js +0 -6
- package/dist/constructor/genFx.js.map +0 -1
- package/dist/operator/catch.d.ts +0 -3
- package/dist/operator/catch.d.ts.map +0 -1
- package/dist/operator/catch.js +0 -9
- package/dist/operator/catch.js.map +0 -1
- package/dist/operator/duringEffect.d.ts +0 -2
- package/dist/operator/duringEffect.d.ts.map +0 -1
- package/dist/operator/duringEffect.js +0 -2
- package/dist/operator/duringEffect.js.map +0 -1
- package/dist/operator/orDie.d.ts +0 -2
- package/dist/operator/orDie.d.ts.map +0 -1
- package/dist/operator/orDie.js +0 -2
- package/dist/operator/orDie.js.map +0 -1
- package/dist/operator/orDieWith.d.ts +0 -2
- package/dist/operator/orDieWith.d.ts.map +0 -1
- package/dist/operator/orDieWith.js +0 -2
- package/dist/operator/orDieWith.js.map +0 -1
- package/dist/operator/orElseCause.d.ts +0 -2
- package/dist/operator/orElseCause.d.ts.map +0 -1
- package/dist/operator/orElseCause.js +0 -2
- package/dist/operator/orElseCause.js.map +0 -1
- package/dist/operator/orElseEither.d.ts +0 -2
- package/dist/operator/orElseEither.d.ts.map +0 -1
- package/dist/operator/orElseEither.js +0 -2
- package/dist/operator/orElseEither.js.map +0 -1
- package/dist/operator/orElseFail.d.ts +0 -2
- package/dist/operator/orElseFail.d.ts.map +0 -1
- package/dist/operator/orElseFail.js +0 -2
- package/dist/operator/orElseFail.js.map +0 -1
- package/dist/operator/orElseSucceed.d.ts +0 -2
- package/dist/operator/orElseSucceed.d.ts.map +0 -1
- package/dist/operator/orElseSucceed.js +0 -2
- package/dist/operator/orElseSucceed.js.map +0 -1
- package/dist/operator/sampleEffect.d.ts +0 -10
- package/dist/operator/sampleEffect.d.ts.map +0 -1
- package/dist/operator/sampleEffect.js +0 -17
- package/dist/operator/sampleEffect.js.map +0 -1
- package/dist/operator/skipAfterEffect.d.ts +0 -2
- package/dist/operator/skipAfterEffect.d.ts.map +0 -1
- package/dist/operator/skipAfterEffect.js +0 -2
- package/dist/operator/skipAfterEffect.js.map +0 -1
- package/dist/operator/skipUntilEffect.d.ts +0 -2
- package/dist/operator/skipUntilEffect.d.ts.map +0 -1
- package/dist/operator/skipUntilEffect.js +0 -2
- package/dist/operator/skipUntilEffect.js.map +0 -1
- package/dist/operator/skipWhileEffect.d.ts +0 -2
- package/dist/operator/skipWhileEffect.d.ts.map +0 -1
- package/dist/operator/skipWhileEffect.js +0 -2
- package/dist/operator/skipWhileEffect.js.map +0 -1
- package/dist/operator/suceed.d.ts +0 -2
- package/dist/operator/suceed.d.ts.map +0 -1
- package/dist/operator/suceed.js +0 -2
- package/dist/operator/suceed.js.map +0 -1
- package/dist/operator/takeUntilEffect.d.ts +0 -2
- package/dist/operator/takeUntilEffect.d.ts.map +0 -1
- package/dist/operator/takeUntilEffect.js +0 -2
- package/dist/operator/takeUntilEffect.js.map +0 -1
- package/dist/operator/takeWhileEffect.d.ts +0 -2
- package/dist/operator/takeWhileEffect.d.ts.map +0 -1
- package/dist/operator/takeWhileEffect.js +0 -2
- package/dist/operator/takeWhileEffect.js.map +0 -1
|
@@ -3,17 +3,12 @@ import { type MutableRef, make } from '@effect/data/MutableRef'
|
|
|
3
3
|
import { type Option, none } from '@effect/data/Option'
|
|
4
4
|
import * as Effect from '@effect/io/Effect'
|
|
5
5
|
|
|
6
|
-
import type { Fx, Sink } from '../Fx.js'
|
|
7
6
|
import { never } from '../constructor/never.js'
|
|
8
7
|
import { HoldFx } from '../operator/hold.js'
|
|
9
8
|
|
|
10
9
|
import { isSubject, Subject } from './Subject.js'
|
|
11
10
|
|
|
12
|
-
export interface HoldSubject<E, A>
|
|
13
|
-
extends Fx<never, E, A>,
|
|
14
|
-
Sink<never, E, A>,
|
|
15
|
-
Subject.Variance<E, A>,
|
|
16
|
-
HoldSubject.Variance<E, A> {
|
|
11
|
+
export interface HoldSubject<E, A> extends Subject<E, A>, HoldSubject.Variance<E, A> {
|
|
17
12
|
readonly value: Effect.Effect<never, never, Option<A>>
|
|
18
13
|
}
|
|
19
14
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { deepStrictEqual } from 'assert'
|
|
2
|
+
|
|
3
|
+
import * as Effect from '@effect/io/Effect'
|
|
4
|
+
import { describe, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { makeRef, RefSubject } from './RefSubject.js'
|
|
7
|
+
|
|
8
|
+
describe(import.meta.url, () => {
|
|
9
|
+
describe('RefSubject.tuple', () => {
|
|
10
|
+
it('allows joining RefSubjects into a single interface', async () => {
|
|
11
|
+
const test = Effect.scoped(
|
|
12
|
+
Effect.gen(function* ($) {
|
|
13
|
+
const a = yield* $(makeRef(() => 1))
|
|
14
|
+
const b = yield* $(makeRef(() => 2))
|
|
15
|
+
const c = yield* $(makeRef(() => 3))
|
|
16
|
+
const tupled = yield* $(RefSubject.tuple(a, b, c))
|
|
17
|
+
|
|
18
|
+
deepStrictEqual(yield* $(tupled.get), [1, 2, 3])
|
|
19
|
+
|
|
20
|
+
yield* $(b.set(4))
|
|
21
|
+
|
|
22
|
+
deepStrictEqual(yield* $(tupled.get), [1, 4, 3])
|
|
23
|
+
|
|
24
|
+
yield* $(tupled.set([5, 6, 7]))
|
|
25
|
+
|
|
26
|
+
deepStrictEqual(yield* $(a.get), 5)
|
|
27
|
+
deepStrictEqual(yield* $(b.get), 6)
|
|
28
|
+
deepStrictEqual(yield* $(c.get), 7)
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
await Effect.runPromise(test)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('RefSubject.struct', () => {
|
|
37
|
+
it('allows joining RefSubjects into a single interface', async () => {
|
|
38
|
+
const test = Effect.scoped(
|
|
39
|
+
Effect.gen(function* ($) {
|
|
40
|
+
const a = yield* $(makeRef(() => 1))
|
|
41
|
+
const b = yield* $(makeRef(() => 2))
|
|
42
|
+
const c = yield* $(makeRef(() => 3))
|
|
43
|
+
const struct = yield* $(RefSubject.struct({ a, b, c }))
|
|
44
|
+
|
|
45
|
+
deepStrictEqual(yield* $(struct.get), { a: 1, b: 2, c: 3 })
|
|
46
|
+
|
|
47
|
+
yield* $(b.set(4))
|
|
48
|
+
|
|
49
|
+
deepStrictEqual(yield* $(struct.get), { a: 1, b: 4, c: 3 })
|
|
50
|
+
|
|
51
|
+
yield* $(struct.set({ a: 5, b: 6, c: 7 }))
|
|
52
|
+
|
|
53
|
+
deepStrictEqual(yield* $(a.get), 5)
|
|
54
|
+
deepStrictEqual(yield* $(b.get), 6)
|
|
55
|
+
deepStrictEqual(yield* $(c.get), 7)
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
await Effect.runPromise(test)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('Computed', () => {
|
|
64
|
+
it('allows computing values from a RefSubject', async () => {
|
|
65
|
+
const test = Effect.scoped(
|
|
66
|
+
Effect.gen(function* ($) {
|
|
67
|
+
const refSubject = yield* $(makeRef(() => 1))
|
|
68
|
+
const computed = yield* $(refSubject.computeSync((a) => a + 42))
|
|
69
|
+
|
|
70
|
+
deepStrictEqual(yield* $(computed.get), 43)
|
|
71
|
+
|
|
72
|
+
yield* $(refSubject.set(2))
|
|
73
|
+
|
|
74
|
+
deepStrictEqual(yield* $(computed.get), 44)
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
await Effect.runPromise(test)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -1,57 +1,65 @@
|
|
|
1
1
|
import { equals } from '@effect/data/Equal'
|
|
2
|
-
import { identity, pipe } from '@effect/data/Function'
|
|
2
|
+
import { identity, pipe, dual, flow } from '@effect/data/Function'
|
|
3
3
|
import * as MutableRef from '@effect/data/MutableRef'
|
|
4
4
|
import * as Option from '@effect/data/Option'
|
|
5
|
+
import * as ReadonlyArray from '@effect/data/ReadonlyArray'
|
|
6
|
+
import * as ReadonlyRecord from '@effect/data/ReadonlyRecord'
|
|
7
|
+
import * as Equivalence from '@effect/data/typeclass/Equivalence'
|
|
5
8
|
import * as Effect from '@effect/io/Effect'
|
|
9
|
+
import type * as Exit from '@effect/io/Exit'
|
|
6
10
|
import * as Ref from '@effect/io/Ref'
|
|
11
|
+
import type * as Scope from '@effect/io/Scope'
|
|
7
12
|
|
|
8
|
-
import { Fx } from '../Fx.js'
|
|
13
|
+
import { Fx, Sink } from '../Fx.js'
|
|
14
|
+
import { flatMapEffect } from '../operator/flatMapEffect.js'
|
|
15
|
+
import { hold } from '../operator/hold.js'
|
|
16
|
+
import { skipWhile } from '../operator/skipWhile.js'
|
|
17
|
+
import { observe } from '../run/observe.js'
|
|
9
18
|
|
|
10
19
|
import { HoldSubject, isHoldSubject } from './HoldSubject.js'
|
|
11
20
|
import { Subject } from './Subject.js'
|
|
12
21
|
|
|
13
22
|
export interface RefSubject<A> extends HoldSubject<never, A>, Ref.Ref<A> {
|
|
23
|
+
readonly eq: Equivalence.Equivalence<A>
|
|
14
24
|
readonly get: Effect.Effect<never, never, A>
|
|
15
25
|
readonly set: (a: A) => Effect.Effect<never, never, A>
|
|
16
26
|
readonly update: (f: (a: A) => A) => Effect.Effect<never, never, A>
|
|
17
|
-
readonly delete: Effect.Effect<never, never,
|
|
27
|
+
readonly delete: Effect.Effect<never, never, A>
|
|
28
|
+
|
|
29
|
+
readonly compute: <R, E, B>(
|
|
30
|
+
f: (a: A) => Effect.Effect<R, E, B>,
|
|
31
|
+
) => Effect.Effect<R | Scope.Scope, never, Computed<E, B>>
|
|
32
|
+
|
|
33
|
+
readonly computeSync: <B>(f: (a: A) => B) => Effect.Effect<Scope.Scope, never, Computed<never, B>>
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
export function makeRef<A>(
|
|
21
37
|
initial: () => A,
|
|
22
|
-
eq:
|
|
38
|
+
eq: Equivalence.Equivalence<A> = equals,
|
|
23
39
|
): Effect.Effect<never, never, RefSubject<A>> {
|
|
24
40
|
return Effect.sync(() => RefSubject.unsafeMake(initial, eq))
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
export namespace RefSubject {
|
|
44
|
+
export type ValueOf<T> = T extends RefSubject<infer A> ? A : never
|
|
45
|
+
|
|
28
46
|
export function unsafeMake<A>(
|
|
29
47
|
initial: () => A,
|
|
30
|
-
eq:
|
|
48
|
+
eq: Equivalence.Equivalence<A> = equals,
|
|
31
49
|
): RefSubject<A> {
|
|
32
|
-
const
|
|
33
|
-
const subject = HoldSubject.unsafeMake<never, A>(
|
|
50
|
+
const current = MutableRef.make(Option.some(initial()))
|
|
51
|
+
const subject = HoldSubject.unsafeMake<never, A>(current)
|
|
34
52
|
|
|
35
|
-
const getValue = () =>
|
|
36
|
-
pipe(
|
|
37
|
-
mutableRef.get(),
|
|
38
|
-
Option.getOrElse(() => {
|
|
39
|
-
const a = initial()
|
|
53
|
+
const getValue = () => pipe(current.get(), Option.getOrElse(initial))
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return a
|
|
44
|
-
}),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
const modify = <B>(f: (a: A) => [B, A]): Effect.Effect<never, never, B> =>
|
|
55
|
+
const modify = <B>(f: (a: A) => readonly [B, A]): Effect.Effect<never, never, B> =>
|
|
48
56
|
Effect.suspendSucceed(() => {
|
|
49
|
-
const
|
|
50
|
-
const [b, a] = f(
|
|
57
|
+
const currentValue = getValue()
|
|
58
|
+
const [b, a] = f(currentValue)
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
current.set(Option.some(a))
|
|
53
61
|
|
|
54
|
-
if (eq(
|
|
62
|
+
if (eq(currentValue, a)) {
|
|
55
63
|
return Effect.succeed(b)
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -70,30 +78,242 @@ export namespace RefSubject {
|
|
|
70
78
|
error: subject.error.bind(subject),
|
|
71
79
|
end: subject.end,
|
|
72
80
|
value: subject.value,
|
|
81
|
+
eq,
|
|
73
82
|
modify,
|
|
74
|
-
|
|
75
|
-
return Ref.get(refSubject)
|
|
76
|
-
},
|
|
77
|
-
set(a: A) {
|
|
78
|
-
return Ref.updateAndGet(() => a)(refSubject)
|
|
79
|
-
},
|
|
80
|
-
update(f: (a: A) => A) {
|
|
81
|
-
return Ref.updateAndGet(f)(refSubject)
|
|
82
|
-
},
|
|
83
|
+
...makeDerivations(modify, Effect.sync(getValue)),
|
|
83
84
|
delete: Effect.sync(() => {
|
|
84
|
-
const option =
|
|
85
|
+
const option = current.get()
|
|
86
|
+
const reset = initial()
|
|
87
|
+
current.set(Option.some(reset))
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
mutableRef.set(Option.none())
|
|
88
|
-
|
|
89
|
-
return option
|
|
89
|
+
return Option.getOrElse(option, () => reset)
|
|
90
90
|
}),
|
|
91
|
+
compute: (f) => makeComputed(refSubject, f),
|
|
92
|
+
computeSync: (f) => makeComputed(refSubject, (a) => Effect.sync(() => f(a))),
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
return refSubject
|
|
94
96
|
}
|
|
97
|
+
|
|
98
|
+
export function tuple<Subjects extends ReadonlyArray.NonEmptyReadonlyArray<RefSubject<any>>>(
|
|
99
|
+
...subjects: Subjects
|
|
100
|
+
): Effect.Effect<
|
|
101
|
+
Scope.Scope,
|
|
102
|
+
never,
|
|
103
|
+
RefSubject<{ readonly [K in keyof Subjects]: ValueOf<Subjects[K]> }>
|
|
104
|
+
> {
|
|
105
|
+
type Val = { readonly [K in keyof Subjects]: ValueOf<Subjects[K]> }
|
|
106
|
+
const length = subjects.length
|
|
107
|
+
|
|
108
|
+
const getUnderlyingValues = Effect.tuple(
|
|
109
|
+
...ReadonlyArray.mapNonEmpty(subjects, (s) => s.get),
|
|
110
|
+
) as Effect.Effect<never, never, Val>
|
|
111
|
+
|
|
112
|
+
const eq = Equivalence.tuple(
|
|
113
|
+
...ReadonlyArray.mapNonEmpty(subjects, (s) => s.eq),
|
|
114
|
+
) as Equivalence.Equivalence<Val>
|
|
115
|
+
|
|
116
|
+
return Effect.gen(function* ($) {
|
|
117
|
+
const initial = yield* $(getUnderlyingValues)
|
|
118
|
+
const refSubject = unsafeMake(() => initial, eq)
|
|
119
|
+
|
|
120
|
+
const updateAtIndex =
|
|
121
|
+
<I extends keyof Val & number>(i: I) =>
|
|
122
|
+
(a: Val[I]) =>
|
|
123
|
+
Effect.gen(function* ($) {
|
|
124
|
+
const current = yield* $(refSubject.get)
|
|
125
|
+
const next = current.slice()
|
|
126
|
+
next[i] = a
|
|
127
|
+
|
|
128
|
+
yield* $(refSubject.set(next as Val))
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Override event to replicate events into underlying subjects
|
|
132
|
+
const event = (val: Val) =>
|
|
133
|
+
Effect.tuple(...ReadonlyArray.mapNonEmpty(subjects, (s, i) => s.event(val[i])))
|
|
134
|
+
|
|
135
|
+
// Override modify to replicate events into underlying subjects
|
|
136
|
+
const modify = <B>(f: (a: Val) => readonly [B, Val]): Effect.Effect<never, never, B> =>
|
|
137
|
+
Effect.gen(function* ($) {
|
|
138
|
+
const current = yield* $(getUnderlyingValues)
|
|
139
|
+
const [b, a] = f(current)
|
|
140
|
+
|
|
141
|
+
if (!eq(current, a)) {
|
|
142
|
+
// Will update this RefSubject using observe below
|
|
143
|
+
yield* $(event(a))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return b
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Listen to changes to each subject and update the derived subject
|
|
150
|
+
for (let i = 0; i < length; i++) {
|
|
151
|
+
yield* $(Effect.forkScoped(observe(updateAtIndex(i))(subjects[i])))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Allow underlying subscriptions to start
|
|
155
|
+
yield* $(Effect.yieldNow())
|
|
156
|
+
|
|
157
|
+
const derived: RefSubject<Val> = {
|
|
158
|
+
...refSubject,
|
|
159
|
+
event,
|
|
160
|
+
modify,
|
|
161
|
+
...makeDerivations(modify, refSubject.get),
|
|
162
|
+
compute: (f) => makeComputed(derived, f),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return derived
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function struct<Subjects extends Readonly<Record<string, RefSubject<any>>>>(
|
|
170
|
+
subjects: Subjects,
|
|
171
|
+
): Effect.Effect<
|
|
172
|
+
Scope.Scope,
|
|
173
|
+
never,
|
|
174
|
+
RefSubject<{ readonly [K in keyof Subjects]: ValueOf<Subjects[K]> }>
|
|
175
|
+
> {
|
|
176
|
+
type Val = { readonly [K in keyof Subjects]: ValueOf<Subjects[K]> }
|
|
177
|
+
|
|
178
|
+
const getUnderlyingValues = Effect.struct(
|
|
179
|
+
ReadonlyRecord.map(subjects, (s) => s.get),
|
|
180
|
+
) as Effect.Effect<never, never, Val>
|
|
181
|
+
|
|
182
|
+
const eq = Equivalence.struct(
|
|
183
|
+
ReadonlyRecord.map(subjects, (s) => s.eq),
|
|
184
|
+
) as Equivalence.Equivalence<Val>
|
|
185
|
+
|
|
186
|
+
return Effect.gen(function* ($) {
|
|
187
|
+
const initial = yield* $(getUnderlyingValues)
|
|
188
|
+
const refSubject = unsafeMake(() => initial, eq)
|
|
189
|
+
|
|
190
|
+
const updateAtKey =
|
|
191
|
+
<I extends keyof Val & string>(i: I) =>
|
|
192
|
+
(a: Val[I]) =>
|
|
193
|
+
Effect.gen(function* ($) {
|
|
194
|
+
const current = yield* $(refSubject.get)
|
|
195
|
+
const next = { ...current, [i]: a }
|
|
196
|
+
|
|
197
|
+
yield* $(refSubject.set(next as Val))
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Override event to replicate events into underlying subjects
|
|
201
|
+
const event = (val: Val) =>
|
|
202
|
+
Effect.struct(ReadonlyRecord.map(subjects, (s, i) => s.event(val[i])))
|
|
203
|
+
|
|
204
|
+
// Override modify to replicate events into underlying subjects
|
|
205
|
+
const modify = <B>(f: (a: Val) => readonly [B, Val]): Effect.Effect<never, never, B> =>
|
|
206
|
+
Effect.gen(function* ($) {
|
|
207
|
+
const current = yield* $(getUnderlyingValues)
|
|
208
|
+
const [b, a] = f(current)
|
|
209
|
+
|
|
210
|
+
if (!eq(current, a)) {
|
|
211
|
+
// Will update this RefSubject using observe below
|
|
212
|
+
yield* $(event(a))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return b
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// Listen to changes to each subject and update the derived subject
|
|
219
|
+
for (const i in subjects) {
|
|
220
|
+
yield* $(Effect.forkScoped(observe(updateAtKey(i))(subjects[i])))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Allow underlying subscriptions to start
|
|
224
|
+
yield* $(Effect.yieldNow())
|
|
225
|
+
|
|
226
|
+
const derived: RefSubject<Val> = {
|
|
227
|
+
...refSubject,
|
|
228
|
+
event,
|
|
229
|
+
modify,
|
|
230
|
+
...makeDerivations(modify, refSubject.get),
|
|
231
|
+
compute: (f) => makeComputed(derived, f),
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return derived
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const makeDerivations = <A>(
|
|
239
|
+
modify: <B>(f: (a: A) => readonly [B, A]) => Effect.Effect<never, never, B>,
|
|
240
|
+
getValue: Effect.Effect<never, never, A>,
|
|
241
|
+
) => {
|
|
242
|
+
return {
|
|
243
|
+
get: getValue,
|
|
244
|
+
set(a: A) {
|
|
245
|
+
return modify(() => [a, a])
|
|
246
|
+
},
|
|
247
|
+
update(f: (a: A) => A) {
|
|
248
|
+
return modify((a) => {
|
|
249
|
+
const a2 = f(a)
|
|
250
|
+
return [a2, a2]
|
|
251
|
+
})
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
}
|
|
95
255
|
}
|
|
96
256
|
|
|
97
257
|
export function isRefSubject<A>(u: unknown): u is RefSubject<A> {
|
|
98
258
|
return isHoldSubject(u) && Ref.RefTypeId in u
|
|
99
259
|
}
|
|
260
|
+
|
|
261
|
+
export interface Computed<E, A> extends Fx<never, E, A> {
|
|
262
|
+
readonly get: Effect.Effect<never, E, A>
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export namespace Computed {
|
|
266
|
+
export function make<A, R, E, B>(
|
|
267
|
+
input: RefSubject<A>,
|
|
268
|
+
compute: (a: A) => Effect.Effect<R, E, B>,
|
|
269
|
+
): Effect.Effect<R | Scope.Scope, never, Computed<E, B>> {
|
|
270
|
+
return Effect.gen(function* ($) {
|
|
271
|
+
const compute_ = flow(compute, Effect.exit)
|
|
272
|
+
const initialInput = yield* $(input.get)
|
|
273
|
+
const initial = yield* $(compute_(initialInput))
|
|
274
|
+
const refSubject = yield* $(makeRef(() => initial))
|
|
275
|
+
|
|
276
|
+
yield* $(
|
|
277
|
+
pipe(
|
|
278
|
+
input,
|
|
279
|
+
skipWhile((x) => input.eq(x, initialInput)), // We already have the initial value, don't waste downstream resources
|
|
280
|
+
observe((a) => Effect.flatMap(compute_(a), refSubject.set)),
|
|
281
|
+
Effect.forkScoped,
|
|
282
|
+
),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// Allow subscription to finalize as HoldFx under the hood of RefSubject schedules the start of the Fx.
|
|
286
|
+
yield* $(Effect.yieldNow())
|
|
287
|
+
|
|
288
|
+
return new ComputedImpl(refSubject)
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
class ComputedImpl<E, A> extends Fx.Variance<never, E, A> implements Computed<E, A> {
|
|
293
|
+
readonly fx: Fx<never, E, A>
|
|
294
|
+
|
|
295
|
+
constructor(readonly input: RefSubject<Exit.Exit<E, A>>) {
|
|
296
|
+
super()
|
|
297
|
+
|
|
298
|
+
this.fx = pipe(input, flatMapEffect(Effect.done), hold)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
run<R3>(sink: Sink<R3, E, A>) {
|
|
302
|
+
return this.fx.run(sink)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
readonly get = Effect.flatMap(this.input.get, Effect.done)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export const makeComputed: {
|
|
310
|
+
<A, R, E, B>(input: RefSubject<A>, compute: (a: A) => Effect.Effect<R, E, B>): Effect.Effect<
|
|
311
|
+
R | Scope.Scope,
|
|
312
|
+
never,
|
|
313
|
+
Computed<E, B>
|
|
314
|
+
>
|
|
315
|
+
|
|
316
|
+
<A, R, E, B>(compute: (a: A) => Effect.Effect<R, E, B>): (
|
|
317
|
+
input: RefSubject<A>,
|
|
318
|
+
) => Effect.Effect<R | Scope.Scope, never, Computed<E, B>>
|
|
319
|
+
} = dual(2, Computed.make)
|
|
@@ -9,7 +9,7 @@ import * as Synchronized from '@effect/io/Ref/Synchronized'
|
|
|
9
9
|
import { Fx } from '../Fx.js'
|
|
10
10
|
|
|
11
11
|
import { HoldSubject } from './HoldSubject.js'
|
|
12
|
-
import { isRefSubject, RefSubject } from './RefSubject.js'
|
|
12
|
+
import { Computed, isRefSubject, RefSubject } from './RefSubject.js'
|
|
13
13
|
import { Subject } from './Subject.js'
|
|
14
14
|
|
|
15
15
|
export interface SynchronizedSubject<A> extends RefSubject<A>, Synchronized.Synchronized<A> {
|
|
@@ -30,8 +30,7 @@ export namespace SynchronizedSubject {
|
|
|
30
30
|
): SynchronizedSubject<A> {
|
|
31
31
|
const mutableRef = MutableRef.make(Option.some(initial()))
|
|
32
32
|
const subject = HoldSubject.unsafeMake<never, A>(mutableRef)
|
|
33
|
-
const
|
|
34
|
-
const locked = semaphore.withPermits(1)
|
|
33
|
+
const locked = Effect.unsafeMakeSemaphore(1).withPermits(1)
|
|
35
34
|
|
|
36
35
|
const getValue = () =>
|
|
37
36
|
pipe(
|
|
@@ -83,6 +82,7 @@ export namespace SynchronizedSubject {
|
|
|
83
82
|
error: subject.error.bind(subject),
|
|
84
83
|
end: subject.end,
|
|
85
84
|
value: subject.value,
|
|
85
|
+
eq,
|
|
86
86
|
modify,
|
|
87
87
|
modifyEffect,
|
|
88
88
|
get get() {
|
|
@@ -98,13 +98,14 @@ export namespace SynchronizedSubject {
|
|
|
98
98
|
return Synchronized.updateAndGetEffect(f)(synchronizedSubject)
|
|
99
99
|
},
|
|
100
100
|
delete: Effect.sync(() => {
|
|
101
|
-
const
|
|
101
|
+
const value = getValue()
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
mutableRef.set(Option.none())
|
|
103
|
+
mutableRef.set(Option.some(value))
|
|
105
104
|
|
|
106
|
-
return
|
|
105
|
+
return value
|
|
107
106
|
}),
|
|
107
|
+
compute: (f) => Computed.make(synchronizedSubject, f),
|
|
108
|
+
computeSync: (f) => Computed.make(synchronizedSubject, (a) => Effect.sync(() => f(a))),
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
return synchronizedSubject
|
|
@@ -16,7 +16,7 @@ export class MulticastFx<R, E, A>
|
|
|
16
16
|
extends Fx.Variance<R, E, A>
|
|
17
17
|
implements Fx<R, E, A>, Fx.Sink<never, E, A>
|
|
18
18
|
{
|
|
19
|
-
|
|
19
|
+
readonly observers: Array<MulticastObserver<any, E, A>> = []
|
|
20
20
|
protected fiber: RuntimeFiber<never, unknown> | undefined
|
|
21
21
|
|
|
22
22
|
constructor(readonly fx: Fx<R, E, A>) {
|