# Effect Lens Next An effectful Lens for Effect v4. `effect-lens-next` provides a bidirectional view into shared state. A Lens can read, observe, and atomically update a value while focusing on a nested part of a larger structure. ## Install ```sh npm install effect@4.0.0-beta.85 effect-lens-next ``` This release targets Effect `4.0.0-beta.85` exactly while Effect v4 is in beta. ## Quickstart ```ts import { Effect, Stream, SubscriptionRef } from "effect" import { Lens } from "effect-lens-next" const program = Effect.gen(function*() { const state = yield* SubscriptionRef.make({ count: 0, label: "Counter" }) const count = state.pipe( Lens.fromSubscriptionRef, Lens.focusObjectOn("count") ) yield* Lens.update(count, (value) => value + 1) const current = yield* Lens.get(count) yield* count.changes.pipe( Stream.take(1), Stream.runForEach((value) => Effect.log(`count: ${value}`)) ) return current }) ``` ## Lens Type ```ts Lens.Lens< A, // focused value ER, // read errors EW, // write errors RR, // read requirements RW // write requirements > ``` ## Constructors - `Lens.fromSubscriptionRef` creates a reactive Lens. - `Lens.fromSynchronizedRef` creates a non-reactive Lens whose stream emits its value when evaluated. - `Lens.fromRef` creates a non-reactive Lens and returns it in an Effect because it allocates a semaphore. - `Lens.make` creates a Lens from custom `get`, `changes`, `commit`, and `lock` operations. - `Lens.unwrap` flattens an effect that produces a Lens. ## Focusing ```ts import { Effect, SubscriptionRef } from "effect" import { Lens } from "effect-lens-next" const program = Effect.gen(function*() { const state = yield* SubscriptionRef.make({ users: [{ name: "Ada", score: 10 }] }) const score = state.pipe( Lens.fromSubscriptionRef, Lens.focusObjectOn("users"), Lens.focusArrayAt(0), Lens.focusObjectOn("score") ) yield* Lens.set(score, 11) }) ``` Available focus operations include object fields, mutable object fields, arrays, mutable arrays, tuples, mutable tuples, `Chunk` values, and `Option` values. Safe indexed operations fail with `Cause.NoSuchElementError` when the index is absent. Effect v4 no longer treats `Option` as an Effect. Custom effectful mappings should convert optional values explicitly: ```ts import { Array, Effect } from "effect" Lens.mapEffect( (values: ReadonlyArray) => Effect.fromOption(Array.get(values, 0)), (values, value) => Effect.fromOption(Array.replace(values, 0, value)) ) ``` ## Updating The update API follows Effect v4's `SubscriptionRef` conventions: - `get`, `set`, `getAndSet`, `setAndGet` - `modify`, `modifyEffect` - `update`, `updateEffect` - `getAndUpdate`, `getAndUpdateEffect` - `updateAndGet`, `updateAndGetEffect` - `modifySome`, `modifySomeEffect` - `getAndUpdateSome`, `getAndUpdateSomeEffect` - `updateSome`, `updateSomeEffect` - `updateSomeAndGet`, `updateSomeAndGetEffect` Conditional updates use `Option.none()` to skip both the write and change notification: ```ts import { Effect, Option } from "effect" yield* Lens.updateSome(lens, (value) => value < 10 ? Option.some(value + 1) : Option.none() ) yield* Lens.updateSomeEffect(lens, (value) => Effect.succeed(value < 10 ? Option.some(value + 1) : Option.none()) ) ``` ## Subscribable Effect v4 removed its `Readable` and `Subscribable` modules. This package provides a small compatible abstraction with `get` and `changes` properties: ```ts import { Lens, Subscribable } from "effect-lens-next" const users = Lens.fromSubscriptionRef(usersRef) const count = Subscribable.focusArrayLength(users) const current = yield* count.get ``` The module includes value transforms, error recovery (`catch`, `catchCause`, `orElse`, `orElseSucceed`, and `retry`), error inspection, `unwrap`, and the same readonly focus helpers used by Lens. A `SubscriptionRef` can first be converted with `Lens.fromSubscriptionRef`, since every Lens is already a Subscribable.