This commit is contained in:
@@ -134,4 +134,4 @@ const count = Subscribable.focusArrayLength(users)
|
||||
const current = yield* count.get
|
||||
```
|
||||
|
||||
The module includes `make`, `map`, `mapEffect`, `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.
|
||||
The module includes `make`, `map`, `mapEffect`, `mapError`, `tapError`, `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.
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Chunk, Effect, SubscriptionRef } from "effect"
|
||||
import { Chunk, Effect, Stream, SubscriptionRef } from "effect"
|
||||
import * as Lens from "./Lens.js"
|
||||
import * as Subscribable from "./Subscribable.js"
|
||||
|
||||
|
||||
describe("Subscribable", () => {
|
||||
test("mapError transforms errors from get and changes", async () => {
|
||||
const source = Subscribable.make({
|
||||
get: Effect.fail("get"),
|
||||
changes: Stream.fail("changes"),
|
||||
})
|
||||
const mapped = source.pipe(Subscribable.mapError((error: string) => `mapped:${error}`))
|
||||
|
||||
const result = await Effect.runPromise(Effect.gen(function*() {
|
||||
const getError = yield* Effect.flip(mapped.get)
|
||||
const changesError = yield* Effect.flip(Stream.runDrain(mapped.changes))
|
||||
return [getError, changesError]
|
||||
}))
|
||||
|
||||
expect(result).toEqual(["mapped:get", "mapped:changes"])
|
||||
})
|
||||
|
||||
test("tapError observes errors from get and changes", async () => {
|
||||
const observed: Array<string> = []
|
||||
const source = Subscribable.make({
|
||||
get: Effect.fail("get"),
|
||||
changes: Stream.fail("changes"),
|
||||
})
|
||||
const tapped = Subscribable.tapError(source, error => Effect.sync(() => observed.push(error)))
|
||||
|
||||
await Effect.runPromise(Effect.gen(function*() {
|
||||
yield* Effect.flip(tapped.get)
|
||||
yield* Effect.flip(Stream.runDrain(tapped.changes))
|
||||
}))
|
||||
|
||||
expect(observed).toEqual(["get", "changes"])
|
||||
})
|
||||
|
||||
test("focusArrayLength reads the current array length and reflects updates", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
|
||||
@@ -100,6 +100,31 @@ export const mapOptionEffect: {
|
||||
})))
|
||||
|
||||
|
||||
/** Maps errors from both the current value and the stream of changes. */
|
||||
export const mapError: {
|
||||
<E, E2>(f: (error: NoInfer<E>) => E2): <A, R>(self: Subscribable<A, E, R>) => Subscribable<A, E2, R>
|
||||
<A, E, R, E2>(self: Subscribable<A, E, R>, f: (error: NoInfer<E>) => E2): Subscribable<A, E2, R>
|
||||
} = Function.dual(2, <A, E, R, E2>(
|
||||
self: Subscribable<A, E, R>,
|
||||
f: (error: NoInfer<E>) => E2,
|
||||
) => make({
|
||||
get get() { return Effect.mapError(self.get, f) },
|
||||
get changes() { return Stream.mapError(self.changes, f) },
|
||||
}))
|
||||
|
||||
/** Runs an effect when either the current value or the stream of changes fails. */
|
||||
export const tapError: {
|
||||
<E, B, E2, R2>(f: (error: NoInfer<E>) => Effect.Effect<B, E2, R2>): <A, R>(self: Subscribable<A, E, R>) => Subscribable<A, E | E2, R | R2>
|
||||
<A, E, R, B, E2, R2>(self: Subscribable<A, E, R>, f: (error: NoInfer<E>) => Effect.Effect<B, E2, R2>): Subscribable<A, E | E2, R | R2>
|
||||
} = Function.dual(2, <A, E, R, B, E2, R2>(
|
||||
self: Subscribable<A, E, R>,
|
||||
f: (error: NoInfer<E>) => Effect.Effect<B, E2, R2>,
|
||||
) => make({
|
||||
get get() { return Effect.tapError(self.get, f) },
|
||||
get changes() { return Stream.tapError(self.changes, f) },
|
||||
}))
|
||||
|
||||
|
||||
/** Narrows the focus to a field of an object. */
|
||||
export const focusObjectOn: {
|
||||
<A extends object, K extends keyof A>(key: K): <E, R>(self: Subscribable<A, E, R>) => Subscribable<A[K], E, R>
|
||||
|
||||
@@ -263,7 +263,7 @@ yield* someFunctionThatShouldOnlyHaveReadonlyAccessToTheState(lens)
|
||||
```
|
||||
|
||||
#### Focusing
|
||||
This library re-exports Effect's `Subscribable` module and adds a few transforms to narrow the focus of `Subscribable`'s, same as Lenses:
|
||||
This library re-exports Effect's `Subscribable` module and adds `mapError`, `tapError`, and a few transforms to narrow the focus of `Subscribable`'s, same as Lenses:
|
||||
```typescript
|
||||
import { Subscribable } from "effect-lens"
|
||||
|
||||
|
||||
@@ -1,9 +1,41 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Chunk, Effect, SubscriptionRef } from "effect"
|
||||
import { Chunk, Effect, Stream, SubscriptionRef } from "effect"
|
||||
import * as Subscribable from "./Subscribable.js"
|
||||
|
||||
|
||||
describe("Subscribable", () => {
|
||||
test("mapError transforms errors from get and changes", async () => {
|
||||
const source = Subscribable.make({
|
||||
get: Effect.fail("get"),
|
||||
changes: Stream.fail("changes"),
|
||||
})
|
||||
const mapped = source.pipe(Subscribable.mapError((error: string) => `mapped:${error}`))
|
||||
|
||||
const result = await Effect.runPromise(Effect.gen(function*() {
|
||||
const getError = yield* Effect.flip(mapped.get)
|
||||
const changesError = yield* Effect.flip(Stream.runDrain(mapped.changes))
|
||||
return [getError, changesError]
|
||||
}))
|
||||
|
||||
expect(result).toEqual(["mapped:get", "mapped:changes"])
|
||||
})
|
||||
|
||||
test("tapError observes errors from get and changes", async () => {
|
||||
const observed: Array<string> = []
|
||||
const source = Subscribable.make({
|
||||
get: Effect.fail("get"),
|
||||
changes: Stream.fail("changes"),
|
||||
})
|
||||
const tapped = Subscribable.tapError(source, error => Effect.sync(() => observed.push(error)))
|
||||
|
||||
await Effect.runPromise(Effect.gen(function*() {
|
||||
yield* Effect.flip(tapped.get)
|
||||
yield* Effect.flip(Stream.runDrain(tapped.changes))
|
||||
}))
|
||||
|
||||
expect(observed).toEqual(["get", "changes"])
|
||||
})
|
||||
|
||||
test("focusArrayLength reads the current array length and reflects updates", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Array, Chunk, Effect, Function, Iterable, Option, Subscribable } from "effect"
|
||||
import { Array, Chunk, Effect, Function, Iterable, Option, Stream, Subscribable } from "effect"
|
||||
import type { NoSuchElementException } from "effect/Cause"
|
||||
|
||||
|
||||
export * from "effect/Subscribable"
|
||||
|
||||
|
||||
/**
|
||||
* Maps over an `Option` value in the `Subscribable`.
|
||||
*/
|
||||
@@ -39,6 +40,46 @@ export const mapOptionEffect: {
|
||||
onNone: () => Effect.succeed(Option.none()),
|
||||
})))
|
||||
|
||||
|
||||
/**
|
||||
* Maps errors from both the current value and the stream of changes.
|
||||
*/
|
||||
export const mapError: {
|
||||
<E, E2>(
|
||||
f: (error: NoInfer<E>) => E2,
|
||||
): <A, R>(self: Subscribable.Subscribable<A, E, R>) => Subscribable.Subscribable<A, E2, R>
|
||||
<A, E, R, E2>(
|
||||
self: Subscribable.Subscribable<A, E, R>,
|
||||
f: (error: NoInfer<E>) => E2,
|
||||
): Subscribable.Subscribable<A, E2, R>
|
||||
} = Function.dual(2, <A, E, R, E2>(
|
||||
self: Subscribable.Subscribable<A, E, R>,
|
||||
f: (error: NoInfer<E>) => E2,
|
||||
): Subscribable.Subscribable<A, E2, R> => Subscribable.make({
|
||||
get: Effect.mapError(self.get, f),
|
||||
changes: Stream.mapError(self.changes, f),
|
||||
}))
|
||||
|
||||
/**
|
||||
* Runs an effect when either the current value or the stream of changes fails.
|
||||
*/
|
||||
export const tapError: {
|
||||
<E, B, E2, R2>(
|
||||
f: (error: NoInfer<E>) => Effect.Effect<B, E2, R2>,
|
||||
): <A, R>(self: Subscribable.Subscribable<A, E, R>) => Subscribable.Subscribable<A, E | E2, R | R2>
|
||||
<A, E, R, B, E2, R2>(
|
||||
self: Subscribable.Subscribable<A, E, R>,
|
||||
f: (error: NoInfer<E>) => Effect.Effect<B, E2, R2>,
|
||||
): Subscribable.Subscribable<A, E | E2, R | R2>
|
||||
} = Function.dual(2, <A, E, R, B, E2, R2>(
|
||||
self: Subscribable.Subscribable<A, E, R>,
|
||||
f: (error: NoInfer<E>) => Effect.Effect<B, E2, R2>,
|
||||
): Subscribable.Subscribable<A, E | E2, R | R2> => Subscribable.make({
|
||||
get: Effect.tapError(self.get, f),
|
||||
changes: Stream.tapError(self.changes, f),
|
||||
}))
|
||||
|
||||
|
||||
/**
|
||||
* Narrows the focus to a field of an object.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user