1/**
2 * Transactional deferred values for coordinating Effect transactions.
3 *
4 * A `TxDeferred<A, E>` is a write-once cell whose completion is a
5 * `Result<A, E>` stored in transactional state. Readers can wait for the value
6 * from inside a transaction: while the cell is empty the transaction retries,
7 * and when another transaction completes the deferred the waiting transaction
8 * can resume with either the success value or the typed failure.
9 *
10 * @since 4.0.0
11 */
12
13import * as Effect from "./Effect.ts"
14import { dual } from "./Function.ts"
15import type { Inspectable } from "./Inspectable.ts"
16import { NodeInspectSymbol, toJson } from "./Inspectable.ts"
17import type { Option } from "./Option.ts"
18import * as O from "./Option.ts"
19import type { Pipeable } from "./Pipeable.ts"
20import { pipeArguments } from "./Pipeable.ts"
21import { hasProperty } from "./Predicate.ts"
22import type { Result } from "./Result.ts"
23import * as Res from "./Result.ts"
24import * as TxRef from "./TxRef.ts"
25
26const TypeId = "~effect/TxDeferred"
27
28/**
29 * A transactional deferred is a write-once cell readable within transactions.
30 * Readers block (retry the transaction) until a value is committed, and writers
31 * succeed only on the first call; subsequent writes return `false`.
32 *
33 * **When to use**
34 *
35 * Use to coordinate transaction-local readers and one-time completion with a
36 * success or failure result.
37 *
38 * **Example** (Completing a transactional deferred)
39 *
40 * ```ts import.meta.vitest
41 * import { Effect, TxDeferred } from "effect"
42 *
43 * const program = Effect.gen(function*() {
44 * const deferred = yield* TxDeferred.make<number>()
45 *
46 * // Complete the deferred
47 * const first = yield* TxDeferred.succeed(deferred, 42)
48 *
49 * // Second write is a no-op
50 * const second = yield* TxDeferred.succeed(deferred, 99)
51 *
52 * // Read the value
53 * const value = yield* TxDeferred.await(deferred)
54 * return [first, second, value]
55 * })
56 *
57 * await Effect.runPromise(program) // => [true, false, 42]
58 * ```
59 *
60 * @category models
61 * @since 4.0.0
62 */
63export interface TxDeferred<in out A, in out E = never> extends Inspectable, Pipeable {
64 readonly [TypeId]: typeof TypeId
65 readonly ref: TxRef.TxRef<Option<Result<A, E>>>
66}
67
68const TxDeferredProto: Omit<TxDeferred<unknown, unknown>, typeof TypeId | "ref"> = {
69 [NodeInspectSymbol](this: TxDeferred<unknown, unknown>) {
70 return toJson(this)
71 },
72 toJSON(this: TxDeferred<unknown, unknown>) {
73 return {
74 _id: "TxDeferred"
75 }
76 },
77 pipe() {
78 return pipeArguments(this, arguments)
79 }
80}
81
82const makeTxDeferred = <A, E>(ref: TxRef.TxRef<Option<Result<A, E>>>): TxDeferred<A, E> => {
83 const self = Object.create(TxDeferredProto)
84 self[TypeId] = TypeId
85 self.ref = ref
86 return self
87}
88
89/**
90 * Creates a new empty `TxDeferred`.
91 *
92 * **When to use**
93 *
94 * Use to create a transactional deferred that can be completed exactly once.
95 *
96 * **Example** (Creating a transactional deferred)
97 *
98 * ```ts import.meta.vitest
99 * import { Effect, Option, TxDeferred } from "effect"
100 *
101 * const program = Effect.gen(function*() {
102 * const deferred = yield* TxDeferred.make<string, Error>()
103 * return yield* TxDeferred.poll(deferred)
104 * })
105 *
106 * await Effect.runPromise(program) // => Option.none()
107 * ```
108 *
109 * @category constructors
110 * @since 2.0.0
111 */
112export const make = <A, E = never>(): Effect.Effect<TxDeferred<A, E>> =>
113 Effect.map(TxRef.make<Option<Result<A, E>>>(O.none()), makeTxDeferred)
114
115/**
116 * Reads the deferred value. Retries the transaction if the deferred has not
117 * been completed yet.
118 *
119 * **Example** (Awaiting a deferred value)
120 *
121 * ```ts import.meta.vitest
122 * import { Effect, TxDeferred } from "effect"
123 *
124 * const program = Effect.gen(function*() {
125 * const deferred = yield* TxDeferred.make<number>()
126 * yield* TxDeferred.succeed(deferred, 42)
127 * return yield* TxDeferred.await(deferred)
128 * })
129 *
130 * await Effect.runPromise(program) // => 42
131 * ```
132 *
133 * @category getters
134 * @since 4.0.0
135 */
136const await_ = <A, E>(self: TxDeferred<A, E>): Effect.Effect<A, E> =>
137 Effect.gen(function*() {
138 const option = yield* TxRef.get(self.ref)
139 if (O.isNone(option)) {
140 return yield* Effect.txRetry
141 }
142 return Res.isSuccess(option.value)
143 ? option.value.success
144 : yield* Effect.fail(option.value.failure)
145 }).pipe(Effect.tx)
146
147export {
148 /**
149 * Reads the deferred value. Retries the transaction if the deferred has not
150 * been completed yet.
151 *
152 * **When to use**
153 *
154 * Use to read the success value of a `TxDeferred` while retrying until the
155 * deferred is completed.
156 *
157 * @see {@link poll} for inspecting the current completion state without retrying the transaction
158 *
159 * @category getters
160 * @since 4.0.0
161 */
162 await_ as await
163}
164
165/**
166 * Reads the current state of the deferred without retrying. Returns `None` if
167 * not yet completed.
168 *
169 * **When to use**
170 *
171 * Use to inspect a `TxDeferred` without retrying when it is not completed yet.
172 *
173 * **Example** (Polling a deferred)
174 *
175 * ```ts import.meta.vitest
176 * import { Effect, Option, Result, TxDeferred } from "effect"
177 *
178 * const program = Effect.gen(function*() {
179 * const deferred = yield* TxDeferred.make<number>()
180 * const before = yield* TxDeferred.poll(deferred)
181 *
182 * yield* TxDeferred.succeed(deferred, 42)
183 * const after = yield* TxDeferred.poll(deferred)
184 * return [before, after]
185 * })
186 *
187 * await Effect.runPromise(program) // => [Option.none(), Option.some(Result.succeed(42))]
188 * ```
189 *
190 * @category getters
191 * @since 2.0.0
192 */
193export const poll = <A, E>(self: TxDeferred<A, E>): Effect.Effect<Option<Result<A, E>>> => TxRef.get(self.ref)
194
195/**
196 * Completes the deferred with a `Result`. Returns `true` if this was the first
197 * completion, `false` if already completed.
198 *
199 * **When to use**
200 *
201 * Use to complete a `TxDeferred` with an already computed `Result`.
202 *
203 * **Example** (Completing with a result)
204 *
205 * ```ts import.meta.vitest
206 * import { Effect, Result, TxDeferred } from "effect"
207 *
208 * const program = Effect.gen(function*() {
209 * const deferred = yield* TxDeferred.make<number, string>()
210 * const first = yield* TxDeferred.done(deferred, Result.succeed(42))
211 * const second = yield* TxDeferred.done(deferred, Result.succeed(99))
212 * return [first, second]
213 * })
214 *
215 * await Effect.runPromise(program) // => [true, false]
216 * ```
217 *
218 * @category mutations
219 * @since 2.0.0
220 */
221export const done: {
222 <A, E>(result: Result<A, E>): (self: TxDeferred<A, E>) => Effect.Effect<boolean>
223 <A, E>(self: TxDeferred<A, E>, result: Result<A, E>): Effect.Effect<boolean>
224} = dual(
225 2,
226 <A, E>(self: TxDeferred<A, E>, result: Result<A, E>): Effect.Effect<boolean> =>
227 TxRef.modify(self.ref, (current) => {
228 if (O.isSome(current)) {
229 return [false, current]
230 }
231 return [true, O.some(result)]
232 })
233)
234
235/**
236 * Completes the deferred with a success value. Returns `true` if this was the
237 * first completion, `false` if already completed.
238 *
239 * **When to use**
240 *
241 * Use to complete a `TxDeferred` with a successful value.
242 *
243 * **Example** (Completing with a success value)
244 *
245 * ```ts import.meta.vitest
246 * import { Effect, TxDeferred } from "effect"
247 *
248 * const program = Effect.gen(function*() {
249 * const deferred = yield* TxDeferred.make<number>()
250 * const first = yield* TxDeferred.succeed(deferred, 42)
251 * const second = yield* TxDeferred.succeed(deferred, 99)
252 * return [first, second]
253 * })
254 *
255 * await Effect.runPromise(program) // => [true, false]
256 * ```
257 *
258 * @category mutations
259 * @since 2.0.0
260 */
261export const succeed: {
262 <A>(value: A): <E>(self: TxDeferred<A, E>) => Effect.Effect<boolean>
263 <A, E>(self: TxDeferred<A, E>, value: A): Effect.Effect<boolean>
264} = dual(
265 2,
266 <A, E>(self: TxDeferred<A, E>, value: A): Effect.Effect<boolean> => done(self, Res.succeed(value))
267)
268
269/**
270 * Completes the deferred with a failure. Returns `true` if this was the first
271 * completion, `false` if already completed.
272 *
273 * **When to use**
274 *
275 * Use to complete a `TxDeferred` with a typed failure value.
276 *
277 * **Example** (Completing with a failure)
278 *
279 * ```ts import.meta.vitest
280 * import { Cause, Effect, Exit, Option, TxDeferred } from "effect"
281 *
282 * const program = Effect.gen(function*() {
283 * const deferred = yield* TxDeferred.make<number, string>()
284 * const first = yield* TxDeferred.fail(deferred, "boom")
285 * const second = yield* TxDeferred.fail(deferred, "boom2")
286 * const exit = yield* Effect.exit(TxDeferred.await(deferred))
287 * return [first, second, exit, Exit.getCause(exit)]
288 * })
289 *
290 * await Effect.runPromise(program) // => [true, false, Exit.fail("boom"), Option.some(Cause.fail("boom"))]
291 * ```
292 *
293 * @category mutations
294 * @since 2.0.0
295 */
296export const fail: {
297 <E>(error: E): <A>(self: TxDeferred<A, E>) => Effect.Effect<boolean>
298 <A, E>(self: TxDeferred<A, E>, error: E): Effect.Effect<boolean>
299} = dual(
300 2,
301 <A, E>(self: TxDeferred<A, E>, error: E): Effect.Effect<boolean> => done(self, Res.fail(error))
302)
303
304/**
305 * Determines if the provided value is a `TxDeferred`.
306 *
307 * **When to use**
308 *
309 * Use to narrow an unknown value before treating it as a transactional deferred.
310 *
311 * **Example** (Checking transactional deferreds)
312 *
313 * ```ts import.meta.vitest
314 * import { Effect, TxDeferred } from "effect"
315 *
316 * const program = Effect.gen(function*() {
317 * const deferred = yield* TxDeferred.make<number>()
318 * return [TxDeferred.isTxDeferred(deferred), TxDeferred.isTxDeferred("not a deferred")]
319 * })
320 *
321 * await Effect.runPromise(program) // => [true, false]
322 * ```
323 *
324 * @category guards
325 * @since 4.0.0
326 */
327export const isTxDeferred = (u: unknown): u is TxDeferred<unknown, unknown> => hasProperty(u, TypeId)