This commit is contained in:
@@ -134,4 +134,4 @@ const count = Subscribable.focusArrayLength(users)
|
|||||||
const current = yield* count.get
|
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 { 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 Lens from "./Lens.js"
|
||||||
import * as Subscribable from "./Subscribable.js"
|
import * as Subscribable from "./Subscribable.js"
|
||||||
|
|
||||||
|
|
||||||
describe("Subscribable", () => {
|
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 () => {
|
test("focusArrayLength reads the current array length and reflects updates", async () => {
|
||||||
const result = await Effect.runPromise(
|
const result = await Effect.runPromise(
|
||||||
Effect.flatMap(
|
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. */
|
/** Narrows the focus to a field of an object. */
|
||||||
export const focusObjectOn: {
|
export const focusObjectOn: {
|
||||||
<A extends object, K extends keyof A>(key: K): <E, R>(self: Subscribable<A, E, R>) => Subscribable<A[K], E, R>
|
<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
|
#### 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
|
```typescript
|
||||||
import { Subscribable } from "effect-lens"
|
import { Subscribable } from "effect-lens"
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,41 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
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"
|
import * as Subscribable from "./Subscribable.js"
|
||||||
|
|
||||||
|
|
||||||
describe("Subscribable", () => {
|
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 () => {
|
test("focusArrayLength reads the current array length and reflects updates", async () => {
|
||||||
const result = await Effect.runPromise(
|
const result = await Effect.runPromise(
|
||||||
Effect.flatMap(
|
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"
|
import type { NoSuchElementException } from "effect/Cause"
|
||||||
|
|
||||||
|
|
||||||
export * from "effect/Subscribable"
|
export * from "effect/Subscribable"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps over an `Option` value in the `Subscribable`.
|
* Maps over an `Option` value in the `Subscribable`.
|
||||||
*/
|
*/
|
||||||
@@ -39,6 +40,46 @@ export const mapOptionEffect: {
|
|||||||
onNone: () => Effect.succeed(Option.none()),
|
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.
|
* Narrows the focus to a field of an object.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user