@typed/fx 0.0.16 → 0.0.18

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/src/Subject.ts CHANGED
@@ -1,20 +1,30 @@
1
1
  import { Cause } from '@effect/core/io/Cause'
2
2
  import * as Effect from '@effect/core/io/Effect'
3
- import { pipe } from '@fp-ts/data/Function'
4
- import { LazyArg } from '@tsplus/stdlib/data/Function'
3
+ import * as Ref from '@effect/core/io/Ref'
4
+ import { AtomicInternal, UnsafeAPI } from '@effect/core/io/Ref/operations/_internal/AtomicInternal'
5
+ import { SynchronizedInternal } from '@effect/core/io/Ref/operations/_internal/SynchronizedInternal'
6
+ import * as TSemaphore from '@effect/core/stm/TSemaphore'
7
+ import { flow } from '@fp-ts/data/Function'
8
+ import { LazyArg, pipe } from '@tsplus/stdlib/data/Function'
5
9
  import * as Maybe from '@tsplus/stdlib/data/Maybe'
6
10
 
7
- import { Fx } from './Fx.js'
11
+ import { Emitter, Fx } from './Fx.js'
8
12
  import { never } from './fromEffect.js'
9
13
  import { Hold } from './hold.js'
10
14
  import { Multicast } from './multicast.js'
11
15
 
12
- export interface Subject<E, A> extends SubjectEmitter<E, A>, Fx<never, E, A> {}
16
+ export interface Subject<E, A> extends Fx<never, E, A>, Emitter<never, E, A>, UnsafeEmitter<E, A> {}
13
17
 
14
- export interface SubjectEmitter<E, A> {
15
- readonly emit: (a: A) => void
16
- readonly failCause: (cause: Cause<E>) => void
17
- readonly end: () => void
18
+ export interface UnsafeEmitter<E, A> {
19
+ readonly unsafeEmit: (a: A) => void
20
+ readonly unsafeFailCause: (cause: Cause<E>) => void
21
+ readonly unsafeEnd: () => void
22
+ }
23
+
24
+ const FX_BRANDING = {
25
+ _R: () => void 0 as never,
26
+ _E: () => void 0 as any,
27
+ _A: () => void 0 as any,
18
28
  }
19
29
 
20
30
  export namespace Subject {
@@ -22,11 +32,15 @@ export namespace Subject {
22
32
  const m = new Multicast<never, E, A>(never)
23
33
 
24
34
  return {
35
+ ...FX_BRANDING,
25
36
  run: m.run.bind(m),
26
- emit: (a) => Effect.unsafeRunAsync(m.emit(a)),
27
- failCause: (c) => Effect.unsafeRunAsync(m.failCause(c)),
28
- end: () => Effect.unsafeRunAsync(m.end),
29
- } as Subject<E, A>
37
+ emit: m.emit.bind(m),
38
+ failCause: m.failCause.bind(m),
39
+ end: m.end,
40
+ unsafeEmit: (a) => Effect.unsafeRunAsync(m.emit(a)),
41
+ unsafeFailCause: (c) => Effect.unsafeRunAsync(m.failCause(c)),
42
+ unsafeEnd: () => Effect.unsafeRunAsync(m.end),
43
+ }
30
44
  }
31
45
  }
32
46
 
@@ -39,34 +53,150 @@ export namespace HoldSubject {
39
53
  const h = new Hold<never, E, A>(never)
40
54
 
41
55
  return {
42
- run: h.run.bind(h),
43
- get: h.get,
44
- emit: (a) => Effect.unsafeRunAsync(h.emit(a)),
45
- failCause: (c) => Effect.unsafeRunAsync(h.failCause(c)),
46
- end: () => Effect.unsafeRunAsync(h.end),
47
- } as HoldSubject<E, A>
56
+ ...FX_BRANDING,
57
+ get: Effect.sync(() => h.value.get),
58
+ run: (e) => h.run(e),
59
+ emit: (a) => h.emit(a),
60
+ failCause: (e) => h.failCause(e),
61
+ end: h.end,
62
+ unsafeEmit: (a) => Effect.unsafeRunAsync(h.emit(a)),
63
+ unsafeFailCause: (c) => Effect.unsafeRunAsync(h.failCause(c)),
64
+ unsafeEnd: () => Effect.unsafeRunAsync(h.end),
65
+ }
48
66
  }
49
67
  }
50
68
 
51
- export interface BehaviorSubject<E, A> extends Subject<E, A> {
52
- readonly get: Effect.Effect<never, never, A>
53
- }
69
+ export interface RefSubject<E, A> extends Subject<E, A>, Ref.Ref<A> {}
54
70
 
55
- export namespace BehaviorSubject {
56
- export const unsafeMake = <E, A>(initial: LazyArg<A>): BehaviorSubject<E, A> => {
71
+ export namespace RefSubject {
72
+ export const unsafeMake = <E, A>(initial: LazyArg<A>): RefSubject<E, A> => {
57
73
  const h = new Hold<never, E, A>(never)
74
+ const maybeRef: Ref.Ref<Maybe.Maybe<A>> = new AtomicInternal(new UnsafeAPI(h.value))
75
+ const ref = emitRefChanges(invmapRef(maybeRef, Maybe.getOrElse(initial), Maybe.some), h)
58
76
 
59
- // We're mutating a protected variable here to ensure an initial value is held.
60
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
61
- // @ts-expect-error
62
- h._value = Maybe.some(initial())
77
+ // Ensure there is always a value in the Ref
78
+ h.value.set(Maybe.some(initial()))
63
79
 
64
80
  return {
81
+ ...FX_BRANDING,
82
+ [Ref.RefSym]: Ref.RefSym,
83
+ [Ref._A]: (_) => _,
84
+ get: ref.get,
85
+ modify: (f) => ref.modify(f),
86
+ set: (a) => ref.set(a),
87
+ getAndSet: (a) => ref.getAndSet(a),
88
+ getAndUpdate: (f) => ref.getAndUpdate(f),
89
+ getAndUpdateSome: (f) => ref.getAndUpdateSome(f),
90
+ modifySome: (fallback, f) => ref.modifySome(fallback, f),
91
+ update: (f) => ref.update(f),
92
+ updateAndGet: (f) => ref.updateAndGet(f),
93
+ updateSome: (f) => ref.updateSome(f),
94
+ updateSomeAndGet: (f) => ref.updateSomeAndGet(f),
65
95
  run: h.run.bind(h),
66
- get: pipe(h.get, Effect.map(Maybe.getOrElse(initial))),
67
- emit: (a) => Effect.unsafeRunAsync(h.emit(a)),
68
- failCause: (c) => Effect.unsafeRunAsync(h.failCause(c)),
69
- end: () => Effect.unsafeRunAsync(h.end),
70
- } as BehaviorSubject<E, A>
96
+ emit: h.emit.bind(h),
97
+ failCause: h.failCause.bind(h),
98
+ end: h.end,
99
+ unsafeEmit: (a) => Effect.unsafeRunAsync(h.emit(a)),
100
+ unsafeFailCause: (c) => Effect.unsafeRunAsync(h.failCause(c)),
101
+ unsafeEnd: () => Effect.unsafeRunAsync(h.end),
102
+ }
103
+ }
104
+ }
105
+
106
+ export interface SynchronizedSubject<E, A> extends RefSubject<E, A>, Ref.Ref.Synchronized<A> {}
107
+
108
+ export namespace SynchronizedSubject {
109
+ export const unsafeMake = <E, A>(initial: LazyArg<A>): SynchronizedSubject<E, A> => {
110
+ const subject = RefSubject.unsafeMake<E, A>(initial)
111
+ const synchronizedRef = new SynchronizedInternal(subject, TSemaphore.unsafeMake(1))
112
+
113
+ return {
114
+ ...subject,
115
+ [Ref.SynchronizedSym]: Ref.SynchronizedSym,
116
+ modifyEffect: (f) => synchronizedRef.modifyEffect(f),
117
+ modifySomeEffect: (fallback, f) => synchronizedRef.modifySomeEffect(fallback, f),
118
+ getAndUpdateEffect: (f) => synchronizedRef.getAndUpdateEffect(f),
119
+ getAndUpdateSomeEffect: (f) => synchronizedRef.getAndUpdateSomeEffect(f),
120
+ updateEffect: (f) => synchronizedRef.updateEffect(f),
121
+ updateAndGetEffect: (f) => synchronizedRef.updateAndGetEffect(f),
122
+ updateSomeEffect: (f) => synchronizedRef.updateSomeEffect(f),
123
+ updateSomeAndGetEffect: (f) => synchronizedRef.updateSomeAndGetEffect(f),
124
+ }
125
+ }
126
+ }
127
+
128
+ function invmapRef<A, B>(ref: Ref.Ref<A>, to: (a: A) => B, from: (b: B) => A): Ref.Ref<B> {
129
+ const get: Ref.Ref<B>['get'] = pipe(ref.get, Effect.map(to))
130
+
131
+ const modify: Ref.Ref<B>['modify'] = (f) =>
132
+ ref.modify((a) => {
133
+ const [b, c] = f(to(a))
134
+ return [b, from(c)]
135
+ })
136
+
137
+ const set: Ref.Ref<B>['set'] = flow(from, ref.set)
138
+
139
+ const getAndSet: Ref.Ref<B>['getAndSet'] = flow(from, ref.getAndSet, Effect.map(to))
140
+
141
+ const getAndUpdate: Ref.Ref<B>['getAndUpdate'] = (f) =>
142
+ pipe(ref.getAndUpdate(flow(to, f, from)), Effect.map(to))
143
+
144
+ const getAndUpdateSome: Ref.Ref<B>['getAndUpdateSome'] = (f) =>
145
+ pipe(ref.getAndUpdateSome(flow(to, f, Maybe.map(from))), Effect.map(to))
146
+
147
+ const modifySome: Ref.Ref<B>['modifySome'] = (fallback, f) =>
148
+ ref.modifySome(fallback, (a) =>
149
+ pipe(
150
+ a,
151
+ to,
152
+ f,
153
+ Maybe.map(([c, b]) => [c, from(b)]),
154
+ ),
155
+ )
156
+
157
+ const update: Ref.Ref<B>['update'] = (f) => ref.update(flow(to, f, from))
158
+
159
+ const updateAndGet: Ref.Ref<B>['updateAndGet'] = (f) =>
160
+ pipe(ref.updateAndGet(flow(to, f, from)), Effect.map(to))
161
+
162
+ const updateSome: Ref.Ref<B>['updateSome'] = (f) => ref.updateSome(flow(to, f, Maybe.map(from)))
163
+
164
+ const updateSomeAndGet: Ref.Ref<B>['updateSomeAndGet'] = (f) =>
165
+ pipe(ref.updateSomeAndGet(flow(to, f, Maybe.map(from))), Effect.map(to))
166
+
167
+ return {
168
+ [Ref.RefSym]: Ref.RefSym,
169
+ [Ref._A]: (_) => _,
170
+ get,
171
+ modify,
172
+ set,
173
+ getAndSet,
174
+ getAndUpdate,
175
+ getAndUpdateSome,
176
+ modifySome,
177
+ update,
178
+ updateAndGet,
179
+ updateSome,
180
+ updateSomeAndGet,
181
+ }
182
+ }
183
+
184
+ function emitRefChanges<A, E>(ref: Ref.Ref<A>, subject: Emitter<never, E, A>): Ref.Ref<A> {
185
+ const andEmitLatestValue = Effect.zipLeft(pipe(ref.get, Effect.flatMap(subject.emit)))
186
+
187
+ return {
188
+ [Ref.RefSym]: Ref.RefSym,
189
+ [Ref._A]: (_) => _,
190
+ get: ref.get,
191
+ modify: (f) => pipe(ref.modify(f), andEmitLatestValue),
192
+ set: (a) => pipe(ref.set(a), andEmitLatestValue),
193
+ getAndSet: (a) => pipe(ref.getAndSet(a), andEmitLatestValue),
194
+ getAndUpdate: (f) => pipe(ref.getAndUpdate(f), andEmitLatestValue),
195
+ getAndUpdateSome: (f) => pipe(ref.getAndUpdateSome(f), andEmitLatestValue),
196
+ modifySome: (fallback, f) => pipe(ref.modifySome(fallback, f), andEmitLatestValue),
197
+ update: (f) => pipe(ref.update(f), andEmitLatestValue),
198
+ updateAndGet: (f) => pipe(ref.updateAndGet(f), andEmitLatestValue),
199
+ updateSome: (f) => pipe(ref.updateSome(f), andEmitLatestValue),
200
+ updateSomeAndGet: (f) => pipe(ref.updateSomeAndGet(f), andEmitLatestValue),
71
201
  }
72
202
  }
package/src/hold.ts CHANGED
@@ -4,6 +4,7 @@ import * as Fiber from '@effect/core/io/Fiber'
4
4
  import * as FiberId from '@effect/core/io/FiberId'
5
5
  import { Scope } from '@effect/core/io/Scope'
6
6
  import { pipe } from '@fp-ts/data/Function'
7
+ import { AtomicReference } from '@tsplus/stdlib/data/AtomicReference'
7
8
  import * as Duration from '@tsplus/stdlib/data/Duration'
8
9
  import * as Maybe from '@tsplus/stdlib/data/Maybe'
9
10
 
@@ -15,7 +16,7 @@ export function hold<R, E, A>(fx: Fx<R, E, A>): Fx<R, E, A> {
15
16
  }
16
17
 
17
18
  export class Hold<R, E, A> extends Multicast<R, E, A> {
18
- protected _value: Maybe.Maybe<A> = Maybe.none
19
+ readonly value: AtomicReference<Maybe.Maybe<A>> = new AtomicReference(Maybe.none)
19
20
  protected _pendingEmitters: Array<readonly [Emitter<unknown, E, A>, A[]]> = []
20
21
  protected _scheduledFiber: Fiber.RealFiber<any, any> | undefined
21
22
 
@@ -23,8 +24,6 @@ export class Hold<R, E, A> extends Multicast<R, E, A> {
23
24
  super(fx)
24
25
  }
25
26
 
26
- readonly get = Effect.sync(() => this._value)
27
-
28
27
  run<R2>(emitter: Emitter<R2, E, A>): Effect.Effect<R | R2 | Scope, never, unknown> {
29
28
  if (this.shouldScheduleFlush()) {
30
29
  return pipe(
@@ -66,14 +65,14 @@ export class Hold<R, E, A> extends Multicast<R, E, A> {
66
65
  }
67
66
 
68
67
  protected shouldScheduleFlush() {
69
- return Maybe.isSome(this._value) && this.observers.length > 0
68
+ return Maybe.isSome(this.value.get) && this.observers.length > 0
70
69
  }
71
70
 
72
71
  protected scheduleFlush<R>(observer: Emitter<R, E, A>) {
73
72
  this._pendingEmitters.push([
74
73
  observer,
75
74
  pipe(
76
- this._value,
75
+ this.value.get,
77
76
  Maybe.fold(
78
77
  () => [],
79
78
  (a) => [a],
@@ -121,7 +120,7 @@ export class Hold<R, E, A> extends Multicast<R, E, A> {
121
120
  }
122
121
 
123
122
  protected addValue(value: A) {
124
- this._value = Maybe.some(value)
123
+ this.value.set(Maybe.some(value))
125
124
 
126
125
  this._pendingEmitters.forEach(([, values]) => {
127
126
  values.push(value)
@@ -10,10 +10,10 @@ describe(import.meta.url, () => {
10
10
  it('should allow converting sync callbacks into an Fx', async () => {
11
11
  const test = pipe(
12
12
  Fx.withEmitter((emitter) => {
13
- emitter.emit(1)
14
- emitter.emit(2)
15
- emitter.emit(3)
16
- emitter.end()
13
+ emitter.unsafeEmit(1)
14
+ emitter.unsafeEmit(2)
15
+ emitter.unsafeEmit(3)
16
+ emitter.unsafeEnd()
17
17
 
18
18
  return Effect.unit
19
19
  }),
@@ -5,10 +5,10 @@ import { Scope } from '@effect/core/io/Scope'
5
5
  import { flow, pipe } from '@fp-ts/data/Function'
6
6
 
7
7
  import { Emitter, Fx } from './Fx.js'
8
- import type { SubjectEmitter } from './Subject.js'
8
+ import type { UnsafeEmitter } from './Subject.js'
9
9
 
10
10
  export function withEmitter<R, E, A>(
11
- f: (emitter: SubjectEmitter<E, A>) => Effect.Canceler<R>,
11
+ f: (emitter: UnsafeEmitter<E, A>) => Effect.Canceler<R>,
12
12
  ): Fx<R, E, A> {
13
13
  return Fx<R, E, A>(<R2>(sink: Emitter<R2, E, A>) => {
14
14
  return Effect.withFiberRuntime<R | R2 | Scope, never, unknown>((fiber, status) => {
@@ -28,12 +28,12 @@ export function withEmitter<R, E, A>(
28
28
  Effect.zipRight(
29
29
  Effect.asyncEffect<R | R2, never, unknown, R | R2, never, any>((cb) => {
30
30
  canceler = f({
31
- emit: (a) =>
31
+ unsafeEmit: (a) =>
32
32
  unsafeRun(sink.emit(a), (exit) =>
33
33
  Exit.isFailure(exit) ? cb(Effect.failCause(exit.cause)) : undefined,
34
34
  ),
35
- failCause: (e) => unsafeRun(sink.failCause(e), flow(Effect.done, cb)),
36
- end: () => unsafeRun(sink.end, flow(Effect.done, cb)),
35
+ unsafeFailCause: (e) => unsafeRun(sink.failCause(e), flow(Effect.done, cb)),
36
+ unsafeEnd: () => unsafeRun(sink.end, flow(Effect.done, cb)),
37
37
  })
38
38
 
39
39
  return canceler