diff --git a/packages/effect-fc/src/ProxyRef.ts b/packages/effect-fc/src/ProxyRef.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/effect-fc/src/Writable.ts b/packages/effect-fc/src/Writable.ts new file mode 100644 index 0000000..4f1411f --- /dev/null +++ b/packages/effect-fc/src/Writable.ts @@ -0,0 +1,112 @@ +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))) diff --git a/packages/effect-fc/src/index.ts b/packages/effect-fc/src/index.ts index b5fe27e..4632d68 100644 --- a/packages/effect-fc/src/index.ts +++ b/packages/effect-fc/src/index.ts @@ -15,3 +15,4 @@ export * as Stream from "./Stream.js" export * as Subscribable from "./Subscribable.js" export * as SubscriptionRef from "./SubscriptionRef.js" export * as SubscriptionSubRef from "./SubscriptionSubRef.js" +export * as Writable from "./Writable.js"