import { Effect, Function, Pipeable, Predicate, Readable } from "effect" import type { NoInfer } from "effect/Types" /** * @category type ids */ export const TypeId: unique symbol = Symbol.for("@effect-fc/Writable") /** * @category type ids */ export type TypeId = typeof TypeId /** * Represents a value that can be written to. * * Dual to `Readable`, which represents a value that can be read. * A `Writable` exposes a `set` operation that accepts a value of type * `A` and returns an `Effect`. * * @category models */ export interface Writable extends Pipeable.Pipeable { readonly [TypeId]: TypeId readonly set: (value: A) => Effect.Effect } /** * A `ReadWritable` is both `Readable` and `Writable` — it can be both read * from and written to. * * @category models */ export interface ReadWritable extends Readable.Readable, Writable {} /** * @category refinements */ export const isWritable = (u: unknown): u is Writable => Predicate.hasProperty(u, TypeId) const Proto: Pick, typeof TypeId | "pipe"> = { [TypeId]: TypeId, pipe(...args: Array) { return Pipeable.pipeArguments(this, args as any) }, } /** * Construct a `Writable` from a `set` function. * * @category constructors */ export const make = (set: (value: A) => Effect.Effect): Writable => { const self = Object.create(Proto) as Writable ;(self as any).set = set return self } /** * Construct a `ReadWritable` from a `get` effect and a `set` function. * * @category constructors */ export const makeReadWritable = (options: { readonly get: Effect.Effect readonly set: (value: A) => Effect.Effect }): ReadWritable => { const self = Object.create(Proto) as ReadWritable ;(self as any).get = options.get ;(self as any).set = options.set ;(self as any)[Readable.TypeId] = Readable.TypeId return self } /** * Transform the input of a `Writable` using a pure function. * The resulting `Writable` accepts values of type `B`, maps them to `A` with * `f`, and then forwards them to the original `set`. * * @category combinators */ export const contramap: { (f: (b: NoInfer) => A): (self: Writable) => Writable (self: Writable, f: (b: NoInfer) => A): Writable } = Function.dual(2, (self: Writable, f: (b: B) => A): Writable => make((b: B) => self.set(f(b))) ) /** * Transform the input of a `Writable` using an effectful function. * * @category combinators */ export const contramapEffect: { (f: (b: NoInfer) => Effect.Effect): (self: Writable) => Writable (self: Writable, f: (b: NoInfer) => Effect.Effect): Writable } = Function.dual(2, (self: Writable, f: (b: B) => Effect.Effect): Writable => make((b: B) => Effect.flatMap(f(b), (a) => self.set(a))) ) /** * Unwrap a `Writable` that is wrapped in an `Effect`. * * @category constructors */ export const unwrap = ( effect: Effect.Effect, E1, R1>, ): Writable => make((value: A) => Effect.flatMap(effect, (w) => w.set(value)))