diff --git a/packages/effect-lens/README.md b/packages/effect-lens/README.md index c976d80..22887dc 100644 --- a/packages/effect-lens/README.md +++ b/packages/effect-lens/README.md @@ -241,8 +241,11 @@ Currently available: | - | - | | `focusObjectOn` | Focuses to the field of an object | | `focusArrayAt` | Focuses to an indexed entry of an array | +| `focusArrayLength` | Focuses to the length of an array | | `focusTupleAt` | Focuses to an indexed entry of a tuple | | `focusChunkAt` | Focuses to an indexed entry of a `Chunk` | +| `focusChunkSize` | Focuses to the size of a `Chunk` | +| `focusIterableSize` | Focuses to the size of an iterable | ## Todo diff --git a/packages/effect-lens/src/Subscribable.test.ts b/packages/effect-lens/src/Subscribable.test.ts new file mode 100644 index 0000000..19d470f --- /dev/null +++ b/packages/effect-lens/src/Subscribable.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, test } from "bun:test" +import { Chunk, Effect, SubscriptionRef } from "effect" +import * as Subscribable from "./Subscribable.js" + + +describe("Subscribable", () => { + test("focusArrayLength reads the current array length and reflects updates", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([1, 2, 3]), + parent => { + const sizeSub = Subscribable.focusArrayLength(parent) + return Effect.flatMap( + sizeSub.get, + initial => Effect.flatMap( + SubscriptionRef.set(parent, [1, 2, 3, 4, 5]), + () => Effect.map(sizeSub.get, next => [initial, next] as const), + ), + ) + }, + ), + ) + + expect(result).toEqual([3, 5]) + }) + + test("focusChunkSize reads the current chunk size and reflects updates", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make(Chunk.make(1, 2) as Chunk.Chunk), + parent => { + const sizeSub = Subscribable.focusChunkSize(parent) + return Effect.flatMap( + sizeSub.get, + initial => Effect.flatMap( + SubscriptionRef.set(parent, Chunk.make(1, 2, 3, 4)), + () => Effect.map(sizeSub.get, next => [initial, next] as const), + ), + ) + }, + ), + ) + + expect(result).toEqual([2, 4]) + }) + + test("focusIterableSize also works for array values", async () => { + const result = await Effect.runPromise( + Effect.flatMap( + SubscriptionRef.make([1, 2, 3]), + parent => { + const sizeSub = Subscribable.focusIterableSize(parent) + return Effect.flatMap( + sizeSub.get, + initial => Effect.flatMap( + SubscriptionRef.set(parent, [1, 2, 3, 4, 5]), + () => Effect.map(sizeSub.get, next => [initial, next] as const), + ), + ) + }, + ), + ) + + expect(result).toEqual([3, 5]) + }) +}) diff --git a/packages/effect-lens/src/Subscribable.ts b/packages/effect-lens/src/Subscribable.ts index 2eb98a5..6a04f69 100644 --- a/packages/effect-lens/src/Subscribable.ts +++ b/packages/effect-lens/src/Subscribable.ts @@ -1,4 +1,4 @@ -import { Array, Chunk, Effect, Function, Option, Subscribable } from "effect" +import { Array, Chunk, Effect, Function, Iterable, Option, Subscribable } from "effect" import type { NoSuchElementException } from "effect/Cause" @@ -71,6 +71,13 @@ export const focusArrayAt: { index: number, ): Subscribable.Subscribable => Subscribable.mapEffect(self, Array.get(index))) +/** + * Narrows the focus to the length of an array. + */ +export const focusArrayLength = ( + self: Subscribable.Subscribable, +): Subscribable.Subscribable => Subscribable.map(self, a => a.length) + /** * Narrows the focus to an indexed element of a readonly tuple. */ @@ -102,3 +109,17 @@ export const focusChunkAt: { self: Subscribable.Subscribable, E, R>, index: number, ): Subscribable.Subscribable => Subscribable.mapEffect(self, Chunk.get(index))) + +/** + * Narrows the focus to the size of a `Chunk`. + */ +export const focusChunkSize = ( + self: Subscribable.Subscribable, E, R>, +): Subscribable.Subscribable => Subscribable.map(self, Chunk.size) + +/** + * Narrows the focus to the size of a `Iterable`. + */ +export const focusIterableSize = ( + self: Subscribable.Subscribable, E, R>, +): Subscribable.Subscribable => Subscribable.map(self, Iterable.size)