@typed/fx 1.5.1 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typed/fx",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "@effect/data": "^0.10.3",
23
23
  "@effect/io": "^0.17.0"
24
24
  },
25
- "gitHead": "eb7de6339939cb3cad08520f2b1d179d333d10ca",
25
+ "gitHead": "4ba4f251d38dec8c5bee4d6f81f33b735338a7be",
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
package/project.json CHANGED
@@ -20,7 +20,12 @@
20
20
  }
21
21
  },
22
22
  "test": {
23
- "executor": "@nrwl/vite:test"
23
+ "executor": "@nrwl/vite:test",
24
+ "configurations": {
25
+ "watch": {
26
+ "watch": true
27
+ }
28
+ }
24
29
  },
25
30
  "build:cjs": {
26
31
  "executor": "nx:run-commands",
@@ -0,0 +1,14 @@
1
+ export interface Mutable<A> {
2
+ readonly get: () => A
3
+ readonly set: (a: A) => A
4
+ }
5
+
6
+ export function Mutable<A>(initial: A) {
7
+ let value = initial
8
+ return {
9
+ get: () => value,
10
+ set: (a: A) => {
11
+ return (value = a)
12
+ },
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ import { Effect, withRuntimeFlags } from '@effect/io/Effect'
2
+ import { CooperativeYielding } from '@effect/io/Fiber/Runtime/Flags'
3
+ import { disable } from '@effect/io/Fiber/Runtime/Flags/Patch'
4
+
5
+ export function disableCooperativeYielding<R, E, A>(effect: Effect<R, E, A>): Effect<R, E, A> {
6
+ return withRuntimeFlags(effect, disable(CooperativeYielding))
7
+ }
@@ -1,5 +1,4 @@
1
1
  import { pipe } from '@effect/data/Function'
2
- import * as MutableRef from '@effect/data/MutableRef'
3
2
  import * as Option from '@effect/data/Option'
4
3
  import type * as Cause from '@effect/io/Cause'
5
4
  import * as Effect from '@effect/io/Effect'
@@ -7,15 +6,15 @@ import * as Fiber from '@effect/io/Fiber'
7
6
  import type { Scope } from '@effect/io/Scope'
8
7
 
9
8
  import type { Fx } from '../Fx.js'
10
- import { asap } from '../_internal/RefCounter.js'
9
+ import { Mutable } from '../_internal/Mutable.js'
11
10
 
12
11
  import { MulticastFx } from './multicast.js'
13
12
 
14
13
  export function hold<R, E, A>(fx: Fx<R, E, A>): Fx<R, E, A> {
15
- return new HoldFx(fx, MutableRef.make(Option.none()))
14
+ return new HoldFx(fx, Mutable(Option.none()))
16
15
  }
17
16
 
18
- export function hold_<R, E, A>(fx: Fx<R, E, A>, value: MutableRef.MutableRef<Option.Option<A>>) {
17
+ export function hold_<R, E, A>(fx: Fx<R, E, A>, value: Mutable<Option.Option<A>>) {
19
18
  return new HoldFx(fx, value)
20
19
  }
21
20
 
@@ -23,10 +22,7 @@ export class HoldFx<R, E, A> extends MulticastFx<R, E, A> implements Fx<R, E, A>
23
22
  protected pendingSinks: Array<readonly [Fx.Sink<any, E, A>, A[]]> = []
24
23
  protected scheduledFiber: Fiber.RuntimeFiber<any, any> | undefined = undefined
25
24
 
26
- constructor(
27
- readonly fx: Fx<R, E, A>,
28
- protected current: MutableRef.MutableRef<Option.Option<A>>,
29
- ) {
25
+ constructor(readonly fx: Fx<R, E, A>, readonly current: Mutable<Option.Option<A>>) {
30
26
  super(fx)
31
27
 
32
28
  this.event = this.event.bind(this)
@@ -35,7 +31,7 @@ export class HoldFx<R, E, A> extends MulticastFx<R, E, A> implements Fx<R, E, A>
35
31
 
36
32
  run<R2>(sink: Fx.Sink<R2, E, A>): Effect.Effect<Scope | R | R2, never, void> {
37
33
  return Effect.suspend(() => {
38
- if (Option.isSome(MutableRef.get(this.current))) {
34
+ if (Option.isSome(this.current.get())) {
39
35
  return pipe(
40
36
  this.scheduleFlush(sink),
41
37
  Effect.flatMap(() => super.run(sink)),
@@ -80,8 +76,7 @@ export class HoldFx<R, E, A> extends MulticastFx<R, E, A> implements Fx<R, E, A>
80
76
  this.pendingSinks.push([
81
77
  sink,
82
78
  pipe(
83
- this.current,
84
- MutableRef.get,
79
+ this.current.get(),
85
80
  Option.match(
86
81
  () => [],
87
82
  (a) => [a],
@@ -98,7 +93,7 @@ export class HoldFx<R, E, A> extends MulticastFx<R, E, A> implements Fx<R, E, A>
98
93
  return pipe(
99
94
  interrupt,
100
95
  Effect.flatMap(() => this.flushPending()),
101
- Effect.scheduleForked(asap),
96
+ Effect.forkScoped,
102
97
  Effect.tap((f) =>
103
98
  Effect.sync(() => {
104
99
  this.scheduledFiber = f
@@ -138,7 +133,7 @@ export class HoldFx<R, E, A> extends MulticastFx<R, E, A> implements Fx<R, E, A>
138
133
  }
139
134
 
140
135
  protected addValue(a: A) {
141
- MutableRef.set(this.current, Option.some(a))
136
+ this.current.set(Option.some(a))
142
137
  this.pendingSinks.forEach(([, events]) => events.push(a))
143
138
  }
144
139
  }
@@ -32,12 +32,12 @@ class SliceSink<R, E, A> implements Fx.Sink<R, E, A> {
32
32
  ) {}
33
33
 
34
34
  event = (a: A) => {
35
- if (this.skip > 0) {
36
- this.skip--
37
- return Effect.unit()
35
+ if (this.take === 0) {
36
+ return this.earlyExit
38
37
  }
39
38
 
40
- if (this.take === 0) {
39
+ if (this.skip > 0) {
40
+ this.skip--
41
41
  return Effect.unit()
42
42
  }
43
43
 
@@ -1,11 +1,13 @@
1
1
  import { deepStrictEqual } from 'assert'
2
2
 
3
3
  import { millis } from '@effect/data/Duration'
4
+ import * as Either from '@effect/data/Either'
4
5
  import { pipe } from '@effect/data/Function'
5
6
  import * as Effect from '@effect/io/Effect'
6
7
  import { describe, it } from 'vitest'
7
8
 
8
9
  import { at } from '../constructor/at.js'
10
+ import { fail } from '../constructor/fail.js'
9
11
  import { collectAll } from '../run/collectAll.js'
10
12
 
11
13
  import { mergeAll } from './merge.js'
@@ -23,5 +25,17 @@ describe(import.meta.url, () => {
23
25
 
24
26
  deepStrictEqual(events, [9])
25
27
  })
28
+
29
+ it('collects inner errors', async () => {
30
+ const test = pipe(
31
+ mergeAll(at(millis(0), 1), at(millis(20), 2), at(millis(40), 3)),
32
+ switchMap((n) => fail(n)),
33
+ collectAll,
34
+ )
35
+
36
+ const either = await Effect.runPromiseEither(test)
37
+
38
+ deepStrictEqual(either, Either.left(1))
39
+ })
26
40
  })
27
41
  })
@@ -3,13 +3,14 @@ import * as Effect from '@effect/io/Effect'
3
3
  import type { Scope } from '@effect/io/Scope'
4
4
 
5
5
  import type { Fx } from '../Fx.js'
6
+ import { disableCooperativeYielding } from '../_internal/disableCooperativeYielding.js'
6
7
 
7
8
  import { run_ } from './run.js'
8
9
 
9
10
  export function observe<A, R2, E2, B>(
10
11
  f: (a: A) => Effect.Effect<R2, E2, B>,
11
12
  ): <R, E>(fx: Fx<R, E, A>) => Effect.Effect<R | R2, E2 | E, void> {
12
- return flow(observe_(f), Effect.scoped)
13
+ return flow(observe_(f), Effect.scoped, disableCooperativeYielding)
13
14
  }
14
15
 
15
16
  export function observeSync<A, B>(
@@ -1,8 +1,8 @@
1
1
  import { identity } from '@effect/data/Function'
2
- import * as MutableRef from '@effect/data/MutableRef'
3
2
  import { type Option, none } from '@effect/data/Option'
4
3
  import * as Effect from '@effect/io/Effect'
5
4
 
5
+ import { Mutable } from '../_internal/Mutable.js'
6
6
  import { never } from '../constructor/never.js'
7
7
  import { HoldFx } from '../operator/hold.js'
8
8
 
@@ -10,6 +10,11 @@ import { isSubject, Subject } from './Subject.js'
10
10
 
11
11
  export interface HoldSubject<E, A> extends Subject<E, A>, HoldSubject.Variance<E, A> {
12
12
  readonly value: Effect.Effect<never, never, Option<A>>
13
+
14
+ /**
15
+ * @internal
16
+ */
17
+ readonly current: Mutable<Option<A>>
13
18
  }
14
19
 
15
20
  export namespace HoldSubject {
@@ -32,7 +37,7 @@ export namespace HoldSubject {
32
37
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
38
  export type OutputsOf<T> = [T] extends [Variance<infer _E, infer A>] ? A : never
34
39
 
35
- export function unsafeMake<E, A>(value: MutableRef.MutableRef<Option<A>> = MutableRef.make(none())): HoldSubject<E, A> {
40
+ export function unsafeMake<E, A>(value: Mutable<Option<A>> = Mutable(none())): HoldSubject<E, A> {
36
41
  return new HoldSubjectImpl(value)
37
42
  }
38
43
 
@@ -43,11 +48,11 @@ export namespace HoldSubject {
43
48
  };
44
49
  readonly [TypeId]: HoldSubject.Variance<E, A>[TypeId] = this[Subject.TypeId]
45
50
 
46
- constructor(value: MutableRef.MutableRef<Option<A>>) {
51
+ constructor(value: Mutable<Option<A>>) {
47
52
  super(never, value)
48
53
  }
49
54
 
50
- readonly value = Effect.sync(() => MutableRef.get(this.current))
55
+ readonly value = Effect.sync(() => this.current.get())
51
56
  }
52
57
  }
53
58
 
@@ -1,8 +1,12 @@
1
1
  import { deepStrictEqual } from 'assert'
2
2
 
3
+ import { millis } from '@effect/data/Duration'
3
4
  import * as Effect from '@effect/io/Effect'
5
+ import * as Fiber from '@effect/io/Fiber'
4
6
  import { describe, it } from 'vitest'
5
7
 
8
+ import { collectAll } from '../run/collectAll.js'
9
+
6
10
  import { makeRef, RefSubject } from './RefSubject.js'
7
11
 
8
12
  describe(import.meta.url, () => {
@@ -77,5 +81,69 @@ describe(import.meta.url, () => {
77
81
 
78
82
  await Effect.runPromise(test)
79
83
  })
84
+
85
+ it('allows computing values from a Computed', async () => {
86
+ const test = Effect.scoped(
87
+ Effect.gen(function* ($) {
88
+ const refSubject = yield* $(makeRef(() => 1))
89
+ const computed = yield* $(refSubject.computeSync((a) => a + 42))
90
+ const computed2 = yield* $(computed.computeSync((a) => a + 1))
91
+
92
+ deepStrictEqual(yield* $(computed.get), 43, '1a -43')
93
+ deepStrictEqual(yield* $(computed2.get), 44, '1b - 44')
94
+
95
+ yield* $(refSubject.set(2))
96
+
97
+ deepStrictEqual(yield* $(computed.get), 44, '2a - 44')
98
+ deepStrictEqual(yield* $(computed2.get), 45, '2a - 45')
99
+ }),
100
+ )
101
+
102
+ await Effect.runPromise(test)
103
+ })
104
+ })
105
+
106
+ it('allows replaying latest events to late subscribers', async () => {
107
+ const test = Effect.gen(function* ($) {
108
+ const holdSubject = RefSubject.unsafeMake<number>(() => 0)
109
+
110
+ const producer = Effect.gen(function* ($) {
111
+ yield* $(Effect.sleep(millis(0)))
112
+ yield* $(holdSubject.event(1))
113
+ yield* $(Effect.sleep(millis(100)))
114
+ yield* $(holdSubject.event(2))
115
+ yield* $(Effect.sleep(millis(100)))
116
+ yield* $(holdSubject.event(3))
117
+ yield* $(Effect.sleep(millis(100)))
118
+ yield* $(holdSubject.event(4))
119
+ yield* $(Effect.sleep(millis(100)))
120
+ yield* $(holdSubject.event(5))
121
+ yield* $(Effect.sleep(millis(100)))
122
+ yield* $(holdSubject.event(6))
123
+ yield* $(holdSubject.end)
124
+ })
125
+
126
+ yield* $(Effect.fork(producer))
127
+
128
+ const fiber1 = yield* $(Effect.fork(collectAll(holdSubject)))
129
+
130
+ yield* $(Effect.sleep(millis(75)))
131
+
132
+ const fiber2 = yield* $(Effect.fork(collectAll(holdSubject)))
133
+
134
+ yield* $(Effect.sleep(millis(75)))
135
+
136
+ const fiber3 = yield* $(Effect.fork(collectAll(holdSubject)))
137
+
138
+ const events1 = yield* $(Fiber.join(fiber1))
139
+ const events2 = yield* $(Fiber.join(fiber2))
140
+ const events3 = yield* $(Fiber.join(fiber3))
141
+
142
+ deepStrictEqual(events1, [0, 1, 2, 3, 4, 5, 6], '1')
143
+ deepStrictEqual(events2, [1, 2, 3, 4, 5, 6], '2')
144
+ deepStrictEqual(events3, [2, 3, 4, 5, 6], '3')
145
+ })
146
+
147
+ await Effect.runPromise(test)
80
148
  })
81
149
  })
@@ -1,18 +1,18 @@
1
1
  import { equals } from '@effect/data/Equal'
2
2
  import { identity, pipe, dual, flow } from '@effect/data/Function'
3
- import * as MutableRef from '@effect/data/MutableRef'
4
3
  import * as Option from '@effect/data/Option'
5
4
  import * as ReadonlyArray from '@effect/data/ReadonlyArray'
6
5
  import * as ReadonlyRecord from '@effect/data/ReadonlyRecord'
7
6
  import * as Equivalence from '@effect/data/typeclass/Equivalence'
7
+ import type * as Cause from '@effect/io/Cause'
8
8
  import * as Effect from '@effect/io/Effect'
9
9
  import type * as Exit from '@effect/io/Exit'
10
10
  import * as Ref from '@effect/io/Ref'
11
11
  import type * as Scope from '@effect/io/Scope'
12
12
 
13
13
  import { Fx, Sink } from '../Fx.js'
14
+ import { Mutable } from '../_internal/Mutable.js'
14
15
  import { flatMapEffect } from '../operator/flatMapEffect.js'
15
- import { hold } from '../operator/hold.js'
16
16
  import { skipWhile } from '../operator/skipWhile.js'
17
17
  import { observe } from '../run/observe.js'
18
18
 
@@ -24,7 +24,7 @@ export interface RefSubject<A> extends HoldSubject<never, A>, Ref.Ref<A> {
24
24
  readonly get: Effect.Effect<never, never, A>
25
25
  readonly set: (a: A) => Effect.Effect<never, never, A>
26
26
  readonly update: (f: (a: A) => A) => Effect.Effect<never, never, A>
27
- readonly delete: Effect.Effect<never, never, A>
27
+ readonly delete: Effect.Effect<never, Cause.NoSuchElementException, A>
28
28
  readonly compute: <R, E, B>(
29
29
  f: (a: A) => Effect.Effect<R, E, B>,
30
30
  ) => Effect.Effect<R | Scope.Scope, never, Computed<E, B>>
@@ -44,18 +44,26 @@ export namespace RefSubject {
44
44
  export function unsafeMake<A>(
45
45
  initial: () => A,
46
46
  eq: Equivalence.Equivalence<A> = equals,
47
+ current: Mutable<Option.Option<A>> = Mutable(Option.some(initial())),
47
48
  ): RefSubject<A> {
48
- const current = MutableRef.make(Option.some(initial()))
49
49
  const subject = HoldSubject.unsafeMake<never, A>(current)
50
50
 
51
- const getValue = () => pipe(current, MutableRef.get, Option.getOrElse(initial))
51
+ const getValue = () =>
52
+ pipe(
53
+ current.get(),
54
+ Option.getOrElse(() => {
55
+ const a = initial()
56
+ current.set(Option.some(a))
57
+ return a
58
+ }),
59
+ )
52
60
 
53
61
  const modify = <B>(f: (a: A) => readonly [B, A]): Effect.Effect<never, never, B> =>
54
62
  Effect.suspend(() => {
55
63
  const currentValue = getValue()
56
64
  const [b, a] = f(currentValue)
57
65
 
58
- MutableRef.set(current, Option.some(a))
66
+ current.set(Option.some(a))
59
67
 
60
68
  if (eq(currentValue, a)) {
61
69
  return Effect.succeed(b)
@@ -76,15 +84,16 @@ export namespace RefSubject {
76
84
  error: subject.error.bind(subject),
77
85
  end: subject.end,
78
86
  value: subject.value,
87
+ current: subject.current,
79
88
  eq,
80
89
  modify,
81
90
  ...makeDerivations(modify, Effect.sync(getValue)),
82
- delete: Effect.sync(() => {
83
- const option = MutableRef.get(current)
84
- const reset = initial()
85
- MutableRef.set(current, Option.some(reset))
91
+ delete: Effect.suspend(() => {
92
+ const option = current.get()
93
+
94
+ current.set(Option.none())
86
95
 
87
- return Option.getOrElse(option, () => reset)
96
+ return option
88
97
  }),
89
98
  compute: (f) => makeComputed(refSubject, f),
90
99
  computeSync: (f) => makeComputed(refSubject, (a) => Effect.sync(() => f(a))),
@@ -258,6 +267,12 @@ export function isRefSubject<A>(u: unknown): u is RefSubject<A> {
258
267
 
259
268
  export interface Computed<E, A> extends Fx<never, E, A> {
260
269
  readonly get: Effect.Effect<never, E, A>
270
+
271
+ readonly compute: <R2, E2, B>(
272
+ f: (a: A) => Effect.Effect<R2, E2, B>,
273
+ ) => Effect.Effect<R2 | Scope.Scope, never, Computed<E | E2, B>>
274
+
275
+ readonly computeSync: <B>(f: (a: A) => B) => Effect.Effect<Scope.Scope, never, Computed<E, B>>
261
276
  }
262
277
 
263
278
  export namespace Computed {
@@ -288,19 +303,23 @@ export namespace Computed {
288
303
  }
289
304
 
290
305
  class ComputedImpl<E, A> extends Fx.Variance<never, E, A> implements Computed<E, A> {
291
- readonly fx: Fx<never, E, A>
292
-
293
306
  constructor(readonly input: RefSubject<Exit.Exit<E, A>>) {
294
307
  super()
295
-
296
- this.fx = pipe(input, flatMapEffect(Effect.done), hold)
297
308
  }
298
309
 
299
310
  run<R3>(sink: Sink<R3, E, A>) {
300
- return this.fx.run(sink)
311
+ return pipe(this.input, flatMapEffect(Effect.done)).run(sink)
301
312
  }
302
313
 
303
314
  readonly get = Effect.flatMap(this.input.get, Effect.done)
315
+
316
+ readonly compute = <R2, E2, B>(
317
+ f: (a: A) => Effect.Effect<R2, E2, B>,
318
+ ): Effect.Effect<R2 | Scope.Scope, never, Computed<E | E2, B>> =>
319
+ Effect.tap(this.input.compute(Effect.flatMap(f)), Effect.yieldNow)
320
+
321
+ readonly computeSync = <B>(f: (a: A) => B): Effect.Effect<Scope.Scope, never, Computed<E, B>> =>
322
+ Effect.tap(this.input.compute(Effect.map(f)), Effect.yieldNow)
304
323
  }
305
324
  }
306
325