/** * A transactional deferred value — a write-once cell that can be read within transactions. * Readers retry until a value is set; once set, the value is immutable. * * @since 4.0.0 */ import * as Effect from "./Effect.ts" import { dual } from "./Function.ts" import type { Inspectable } from "./Inspectable.ts" import { NodeInspectSymbol, toJson } from "./Inspectable.ts" import type { Option } from "./Option.ts" import * as O from "./Option.ts" import type { Pipeable } from "./Pipeable.ts" import { pipeArguments } from "./Pipeable.ts" import { hasProperty } from "./Predicate.ts" import type { Result } from "./Result.ts" import * as Res from "./Result.ts" import * as TxRef from "./TxRef.ts" const TypeId = "~effect/transactions/TxDeferred" /** * A transactional deferred — a write-once cell readable within transactions. * * Readers block (retry the transaction) until a value is committed. * Writers succeed only on the first call; subsequent writes return `false`. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * * // Complete the deferred * const first = yield* TxDeferred.succeed(deferred, 42) * console.log(first) // true * * // Second write is a no-op * const second = yield* TxDeferred.succeed(deferred, 99) * console.log(second) // false * * // Read the value * const value = yield* TxDeferred.await(deferred) * console.log(value) // 42 * }) * ``` * * @since 4.0.0 * @category models */ export interface TxDeferred extends Inspectable, Pipeable { readonly [TypeId]: typeof TypeId readonly ref: TxRef.TxRef>> } const TxDeferredProto: Omit, typeof TypeId | "ref"> = { [NodeInspectSymbol](this: TxDeferred) { return toJson(this) }, toJSON(this: TxDeferred) { return { _id: "TxDeferred" } }, pipe() { return pipeArguments(this, arguments) } } const makeTxDeferred = (ref: TxRef.TxRef>>): TxDeferred => { const self = Object.create(TxDeferredProto) self[TypeId] = TypeId self.ref = ref return self } /** * Creates a new empty `TxDeferred`. * * @example * ```ts * import { Effect, Option, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const state = yield* TxDeferred.poll(deferred) * console.log(Option.isNone(state)) // true * }) * ``` * * @since 4.0.0 * @category constructors */ export const make = (): Effect.Effect> => Effect.map(TxRef.make>>(O.none()), makeTxDeferred) /** * Reads the deferred value. Retries the transaction if the deferred has not * been completed yet. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * yield* TxDeferred.succeed(deferred, 42) * const value = yield* TxDeferred.await(deferred) * console.log(value) // 42 * }) * ``` * * @since 4.0.0 * @category getters */ const await_ = (self: TxDeferred): Effect.Effect => Effect.gen(function*() { const option = yield* TxRef.get(self.ref) if (O.isNone(option)) { return yield* Effect.txRetry } return Res.isSuccess(option.value) ? option.value.success : yield* Effect.fail(option.value.failure) }).pipe(Effect.tx) export { /** * Reads the deferred value. Retries the transaction if the deferred has not * been completed yet. * * @since 4.0.0 * @category getters */ await_ as await } /** * Reads the current state of the deferred without retrying. Returns `None` if * not yet completed. * * @example * ```ts * import { Effect, Option, Result, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const before = yield* TxDeferred.poll(deferred) * console.log(Option.isNone(before)) // true * * yield* TxDeferred.succeed(deferred, 42) * const after = yield* TxDeferred.poll(deferred) * console.log(after) // Some(Success(42)) * }) * ``` * * @since 4.0.0 * @category getters */ export const poll = (self: TxDeferred): Effect.Effect>> => TxRef.get(self.ref) /** * Completes the deferred with a `Result`. Returns `true` if this was the first * completion, `false` if already completed. * * @example * ```ts * import { Effect, Result, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.done(deferred, Result.succeed(42)) * console.log(first) // true * const second = yield* TxDeferred.done(deferred, Result.succeed(99)) * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ export const done: { /** * Completes the deferred with a `Result`. Returns `true` if this was the first * completion, `false` if already completed. * * @example * ```ts * import { Effect, Result, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.done(deferred, Result.succeed(42)) * console.log(first) // true * const second = yield* TxDeferred.done(deferred, Result.succeed(99)) * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ (result: Result): (self: TxDeferred) => Effect.Effect /** * Completes the deferred with a `Result`. Returns `true` if this was the first * completion, `false` if already completed. * * @example * ```ts * import { Effect, Result, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.done(deferred, Result.succeed(42)) * console.log(first) // true * const second = yield* TxDeferred.done(deferred, Result.succeed(99)) * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ (self: TxDeferred, result: Result): Effect.Effect } = dual( 2, (self: TxDeferred, result: Result): Effect.Effect => TxRef.modify(self.ref, (current) => { if (O.isSome(current)) { return [false, current] } return [true, O.some(result)] }) ) /** * Completes the deferred with a success value. Returns `true` if this was the * first completion, `false` if already completed. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.succeed(deferred, 42) * console.log(first) // true * const second = yield* TxDeferred.succeed(deferred, 99) * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ export const succeed: { /** * Completes the deferred with a success value. Returns `true` if this was the * first completion, `false` if already completed. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.succeed(deferred, 42) * console.log(first) // true * const second = yield* TxDeferred.succeed(deferred, 99) * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ (value: A): (self: TxDeferred) => Effect.Effect /** * Completes the deferred with a success value. Returns `true` if this was the * first completion, `false` if already completed. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.succeed(deferred, 42) * console.log(first) // true * const second = yield* TxDeferred.succeed(deferred, 99) * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ (self: TxDeferred, value: A): Effect.Effect } = dual( 2, (self: TxDeferred, value: A): Effect.Effect => done(self, Res.succeed(value)) ) /** * Completes the deferred with a failure. Returns `true` if this was the first * completion, `false` if already completed. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.fail(deferred, "boom") * console.log(first) // true * const second = yield* TxDeferred.fail(deferred, "boom2") * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ export const fail: { /** * Completes the deferred with a failure. Returns `true` if this was the first * completion, `false` if already completed. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.fail(deferred, "boom") * console.log(first) // true * const second = yield* TxDeferred.fail(deferred, "boom2") * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ (error: E): (self: TxDeferred) => Effect.Effect /** * Completes the deferred with a failure. Returns `true` if this was the first * completion, `false` if already completed. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * const first = yield* TxDeferred.fail(deferred, "boom") * console.log(first) // true * const second = yield* TxDeferred.fail(deferred, "boom2") * console.log(second) // false * }) * ``` * * @since 4.0.0 * @category mutations */ (self: TxDeferred, error: E): Effect.Effect } = dual( 2, (self: TxDeferred, error: E): Effect.Effect => done(self, Res.fail(error)) ) /** * Determines if the provided value is a `TxDeferred`. * * @example * ```ts * import { Effect, TxDeferred } from "effect" * * const program = Effect.gen(function*() { * const deferred = yield* TxDeferred.make() * console.log(TxDeferred.isTxDeferred(deferred)) // true * console.log(TxDeferred.isTxDeferred("not a deferred")) // false * }) * ``` * * @since 4.0.0 * @category guards */ export const isTxDeferred = (u: unknown): u is TxDeferred => hasProperty(u, TypeId)