1import { assert, describe, it } from "@effect/vitest"
2import { Effect, Exit, Fiber, Option, Result, TxDeferred } from "effect"
3
4describe("TxDeferred", () => {
5 describe("constructors", () => {
6 it.effect("make creates a deferred that polls as None", () =>
7 Effect.tx(Effect.gen(function*() {
8 const deferred = yield* TxDeferred.make<number>()
9 const state = yield* TxDeferred.poll(deferred)
10 assert.isTrue(Option.isNone(state))
11 })))
12 })
13
14 describe("getters", () => {
15 it.effect("poll returns None on fresh deferred", () =>
16 Effect.tx(Effect.gen(function*() {
17 const deferred = yield* TxDeferred.make<string>()
18 const state = yield* TxDeferred.poll(deferred)
19 assert.isTrue(Option.isNone(state))
20 })))
21
22 it.effect("poll returns Some(Success(value)) after succeed", () =>
23 Effect.tx(Effect.gen(function*() {
24 const deferred = yield* TxDeferred.make<number>()
25 yield* TxDeferred.succeed(deferred, 42)
26 const state = yield* TxDeferred.poll(deferred)
27 assert.isTrue(Option.isSome(state))
28 if (Option.isSome(state)) {
29 assert.isTrue(Result.isSuccess(state.value))
30 if (Result.isSuccess(state.value)) {
31 assert.strictEqual(state.value.success, 42)
32 }
33 }
34 })))
35
36 it.effect("poll returns Some(Failure(error)) after fail", () =>
37 Effect.tx(Effect.gen(function*() {
38 const deferred = yield* TxDeferred.make<number, string>()
39 yield* TxDeferred.fail(deferred, "boom")
40 const state = yield* TxDeferred.poll(deferred)
41 assert.isTrue(Option.isSome(state))
42 if (Option.isSome(state)) {
43 assert.isTrue(Result.isFailure(state.value))
44 if (Result.isFailure(state.value)) {
45 assert.strictEqual(state.value.failure, "boom")
46 }
47 }
48 })))
49
50 it.effect("await returns value after succeed", () =>
51 Effect.tx(Effect.gen(function*() {
52 const deferred = yield* TxDeferred.make<number>()
53 yield* TxDeferred.succeed(deferred, 42)
54 const value = yield* TxDeferred.await(deferred)
55 assert.strictEqual(value, 42)
56 })))
57
58 it.effect("await fails with error after fail", () =>
59 Effect.tx(Effect.gen(function*() {
60 const deferred = yield* TxDeferred.make<number, string>()
61 yield* TxDeferred.fail(deferred, "boom")
62 const exit = yield* Effect.exit(TxDeferred.await(deferred))
63 assert.isTrue(Exit.isFailure(exit))
64 })))
65
66 it.effect("await retries until completed", () =>
67 Effect.tx(Effect.gen(function*() {
68 const deferred = yield* TxDeferred.make<number>()
69 const fiber = yield* Effect.forkChild(TxDeferred.await(deferred))
70 yield* TxDeferred.succeed(deferred, 99)
71 const value = yield* Fiber.join(fiber)
72 assert.strictEqual(value, 99)
73 })))
74 })
75
76 describe("mutations", () => {
77 it.effect("succeed returns true on fresh deferred", () =>
78 Effect.tx(Effect.gen(function*() {
79 const deferred = yield* TxDeferred.make<number>()
80 const result = yield* TxDeferred.succeed(deferred, 42)
81 assert.isTrue(result)
82 })))
83
84 it.effect("succeed returns false if already completed", () =>
85 Effect.tx(Effect.gen(function*() {
86 const deferred = yield* TxDeferred.make<number>()
87 yield* TxDeferred.succeed(deferred, 42)
88 const result = yield* TxDeferred.succeed(deferred, 99)
89 assert.isFalse(result)
90 })))
91
92 it.effect("fail returns true on fresh deferred", () =>
93 Effect.tx(Effect.gen(function*() {
94 const deferred = yield* TxDeferred.make<number, string>()
95 const result = yield* TxDeferred.fail(deferred, "error")
96 assert.isTrue(result)
97 })))
98
99 it.effect("fail returns false if already completed", () =>
100 Effect.tx(Effect.gen(function*() {
101 const deferred = yield* TxDeferred.make<number, string>()
102 yield* TxDeferred.succeed(deferred, 42)
103 const result = yield* TxDeferred.fail(deferred, "error")
104 assert.isFalse(result)
105 })))
106
107 it.effect("done with Result.succeed behaves like succeed", () =>
108 Effect.tx(Effect.gen(function*() {
109 const deferred = yield* TxDeferred.make<number>()
110 const result = yield* TxDeferred.done(deferred, Result.succeed(42))
111 assert.isTrue(result)
112 const value = yield* TxDeferred.await(deferred)
113 assert.strictEqual(value, 42)
114 })))
115
116 it.effect("done with Result.fail behaves like fail", () =>
117 Effect.tx(Effect.gen(function*() {
118 const deferred = yield* TxDeferred.make<number, string>()
119 const result = yield* TxDeferred.done(deferred, Result.fail("error"))
120 assert.isTrue(result)
121 const exit = yield* Effect.exit(TxDeferred.await(deferred))
122 assert.isTrue(Exit.isFailure(exit))
123 })))
124 })
125
126 describe("guards", () => {
127 it.effect("isTxDeferred returns true for deferred, false for others", () =>
128 Effect.tx(Effect.gen(function*() {
129 const deferred = yield* TxDeferred.make<number>()
130 assert.isTrue(TxDeferred.isTxDeferred(deferred))
131 assert.isFalse(TxDeferred.isTxDeferred({ some: "object" }))
132 assert.isFalse(TxDeferred.isTxDeferred(null))
133 assert.isFalse(TxDeferred.isTxDeferred(undefined))
134 assert.isFalse(TxDeferred.isTxDeferred([1]))
135 })))
136 })
137
138 describe("transactional behavior", () => {
139 it.effect("await observes completion made earlier in the same transaction", () =>
140 Effect.gen(function*() {
141 const deferred = yield* TxDeferred.make<number>()
142 const value = yield* Effect.tx(
143 Effect.gen(function*() {
144 yield* TxDeferred.succeed(deferred, 42)
145 return yield* TxDeferred.await(deferred)
146 })
147 )
148 assert.strictEqual(value, 42)
149 }))
150
151 it.effect("two deferreds modified atomically", () =>
152 Effect.gen(function*() {
153 const d1 = yield* TxDeferred.make<number>()
154 const d2 = yield* TxDeferred.make<string>()
155 yield* Effect.tx(
156 Effect.gen(function*() {
157 yield* TxDeferred.succeed(d1, 42)
158 yield* TxDeferred.succeed(d2, "hello")
159 })
160 )
161 const v1 = yield* TxDeferred.await(d1)
162 const v2 = yield* TxDeferred.await(d2)
163 assert.strictEqual(v1, 42)
164 assert.strictEqual(v2, "hello")
165 }))
166 })
167
168 describe("concurrency", () => {
169 it.effect("multiple fibers awaiting same deferred all unblock", () =>
170 Effect.tx(Effect.gen(function*() {
171 const deferred = yield* TxDeferred.make<number>()
172 const f1 = yield* Effect.forkChild(TxDeferred.await(deferred))
173 const f2 = yield* Effect.forkChild(TxDeferred.await(deferred))
174 const f3 = yield* Effect.forkChild(TxDeferred.await(deferred))
175 yield* TxDeferred.succeed(deferred, 42)
176 const [v1, v2, v3] = yield* Effect.all([
177 Fiber.join(f1),
178 Fiber.join(f2),
179 Fiber.join(f3)
180 ])
181 assert.strictEqual(v1, 42)
182 assert.strictEqual(v2, 42)
183 assert.strictEqual(v3, 42)
184 })))
185
186 it.effect("race between succeed and fail: only first wins", () =>
187 Effect.tx(Effect.gen(function*() {
188 const deferred = yield* TxDeferred.make<number, string>()
189 const first = yield* TxDeferred.succeed(deferred, 42)
190 const second = yield* TxDeferred.fail(deferred, "error")
191 assert.isTrue(first)
192 assert.isFalse(second)
193 const value = yield* TxDeferred.await(deferred)
194 assert.strictEqual(value, 42)
195 })))
196 })
197})