0.2.0 #5
@@ -1,11 +1,128 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Chunk, Context, Effect, Option, SubscriptionRef } from "effect"
|
||||
import { Chunk, Context, Effect, Option, Stream, SubscriptionRef } from "effect"
|
||||
import * as Lens from "./Lens.js"
|
||||
|
||||
|
||||
describe("Lens", () => {
|
||||
class Offset extends Context.Tag("Offset")<Offset, { readonly value: number }>() {}
|
||||
|
||||
test("mapErrorRead transforms read errors", async () => {
|
||||
const lens = Lens.mapErrorRead(
|
||||
Lens.make<number, "read", never, never, never>({
|
||||
get: Effect.fail("read" as const),
|
||||
changes: Stream.fail("read" as const),
|
||||
set: () => Effect.void,
|
||||
}),
|
||||
error => `mapped:${ error }`,
|
||||
)
|
||||
|
||||
const result = await Effect.runPromise(Effect.either(Lens.get(lens)))
|
||||
|
||||
expect(result.left).toBe("mapped:read")
|
||||
})
|
||||
|
||||
test("mapErrorWrite transforms modify errors", async () => {
|
||||
const lens = Lens.mapErrorWrite(
|
||||
Lens.make<number, never, "write", never, never>({
|
||||
get: Effect.succeed(1),
|
||||
changes: Stream.make(1),
|
||||
set: () => Effect.fail("write" as const),
|
||||
}),
|
||||
() => "mapped-write",
|
||||
)
|
||||
|
||||
const result = await Effect.runPromise(Effect.either(Lens.set(lens, 2)))
|
||||
|
||||
expect(result.left).toBe("mapped-write")
|
||||
})
|
||||
|
||||
test("mapError transforms read and modify errors", async () => {
|
||||
const lens = Lens.mapError(
|
||||
Lens.make<number, "read", "write", never, never>({
|
||||
get: Effect.fail("read" as const),
|
||||
changes: Stream.fail("read" as const),
|
||||
set: () => Effect.fail("write" as const),
|
||||
}),
|
||||
() => "mapped",
|
||||
)
|
||||
|
||||
const result = await Effect.runPromise(Effect.all([
|
||||
Effect.either(Lens.get(lens)),
|
||||
Effect.either(Lens.set(lens, 1)),
|
||||
] as const))
|
||||
|
||||
expect(result[0].left).toBe("mapped")
|
||||
expect(result[1].left).toBe("mapped")
|
||||
})
|
||||
|
||||
test("catchAllRead recovers from read failures", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make(42),
|
||||
fallback => Lens.get(
|
||||
Lens.catchAllRead(
|
||||
Lens.make<number, "read", never, never, never>({
|
||||
get: Effect.fail("read" as const),
|
||||
changes: Stream.fail("read" as const),
|
||||
set: () => Effect.void,
|
||||
}),
|
||||
() => Lens.fromSubscriptionRef(fallback),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toBe(42)
|
||||
})
|
||||
|
||||
test("tapErrorRead runs an effect on read failures", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make(0),
|
||||
counter => {
|
||||
const lens = Lens.tapErrorRead(
|
||||
Lens.make<number, "read", never, never, never>({
|
||||
get: Effect.fail("read" as const),
|
||||
changes: Stream.fail("read" as const),
|
||||
set: () => Effect.void,
|
||||
}),
|
||||
() => SubscriptionRef.modify(counter, n => [void 0, n + 1] as const),
|
||||
)
|
||||
return Effect.flatMap(
|
||||
Effect.either(Lens.get(lens)),
|
||||
() => counter.get,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toBe(1)
|
||||
})
|
||||
|
||||
test("tapErrorWrite runs an effect on modify failures", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
SubscriptionRef.make(0),
|
||||
counter => {
|
||||
const lens = Lens.tapErrorWrite(
|
||||
Lens.make<number, never, "write", never, never>({
|
||||
get: Effect.succeed(1),
|
||||
changes: Stream.make(1),
|
||||
set: () => Effect.fail("write" as const),
|
||||
}),
|
||||
() => SubscriptionRef.modify(counter, n => [void 0, n + 1] as const),
|
||||
)
|
||||
return Effect.flatMap(
|
||||
Effect.either(Lens.set(lens, 2)),
|
||||
() => counter.get,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
expect(result).toBe(1)
|
||||
})
|
||||
|
||||
test("mapOption transforms Some values and preserves None", async () => {
|
||||
const result = await Effect.runPromise(
|
||||
Effect.flatMap(
|
||||
|
||||
@@ -270,6 +270,173 @@ export const mapStream: {
|
||||
}))
|
||||
|
||||
|
||||
/**
|
||||
* Transforms read errors of a `Lens`.
|
||||
*
|
||||
* Applies to `get` and `changes` while leaving `modify` unchanged.
|
||||
*/
|
||||
export const mapErrorRead: {
|
||||
<A, ER, EW, RR, RW, E2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: NoInfer<ER>) => E2,
|
||||
): Lens<A, E2, EW, RR, RW>
|
||||
<A, ER, EW, RR, RW, E2>(
|
||||
f: (error: NoInfer<ER>) => E2,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, EW, RR, RW>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: NoInfer<ER>) => E2,
|
||||
): Lens<A, E2, EW, RR, RW> => make({
|
||||
get get() { return Effect.mapError(self.get, f) },
|
||||
get changes() { return Stream.mapError(self.changes, f) },
|
||||
get modify() { return self.modify as any },
|
||||
}))
|
||||
|
||||
/**
|
||||
* Transforms modify errors of a `Lens`.
|
||||
*
|
||||
* Applies to the `modify` effect. Since `modify` may also fail with errors coming from the
|
||||
* user-supplied callback, the handler receives `unknown`.
|
||||
*/
|
||||
export const mapErrorWrite: {
|
||||
<A, ER, EW, RR, RW, E2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => E2,
|
||||
): Lens<A, ER, E2, RR, RW>
|
||||
<A, ER, EW, RR, RW, E2>(
|
||||
f: (error: unknown) => E2,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, E2, RR, RW>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => E2,
|
||||
): Lens<A, ER, E2, RR, RW> => make({
|
||||
get get() { return self.get },
|
||||
get changes() { return self.changes },
|
||||
modify: <B, E1 = never, R1 = never>(
|
||||
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
|
||||
) => Effect.mapError(self.modify(g), f),
|
||||
}))
|
||||
|
||||
/**
|
||||
* Transforms all errors of a `Lens`.
|
||||
*
|
||||
* Applies to `get`, `changes`, and `modify`. Since `modify` may also fail with errors coming
|
||||
* from the user-supplied callback, the handler receives `unknown`.
|
||||
*/
|
||||
export const mapError: {
|
||||
<A, ER, EW, RR, RW, E2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => E2,
|
||||
): Lens<A, E2, E2, RR, RW>
|
||||
<A, ER, EW, RR, RW, E2>(
|
||||
f: (error: unknown) => E2,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, E2, RR, RW>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => E2,
|
||||
): Lens<A, E2, E2, RR, RW> => make({
|
||||
get get() { return Effect.mapError(self.get, f) },
|
||||
get changes() { return Stream.mapError(self.changes, f) },
|
||||
modify: <B, E1 = never, R1 = never>(
|
||||
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
|
||||
) => Effect.mapError(self.modify(g), f),
|
||||
}))
|
||||
|
||||
/**
|
||||
* Recovers from read failures of a `Lens`.
|
||||
*
|
||||
* Applies to `get` and `changes` while leaving `modify` unchanged.
|
||||
*/
|
||||
export const catchAllRead: {
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: NoInfer<ER>) => Subscribable.Subscribable<A, E2, R2>,
|
||||
): Lens<A, E2, EW, RR | R2, RW>
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
f: (error: NoInfer<ER>) => Subscribable.Subscribable<A, E2, R2>,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, E2, EW, RR | R2, RW>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: NoInfer<ER>) => Subscribable.Subscribable<A, E2, R2>,
|
||||
): Lens<A, E2, EW, RR | R2, RW> => make({
|
||||
get get() { return Effect.catchAll(self.get, error => f(error).get) },
|
||||
get changes() { return Stream.catchAll(self.changes, error => f(error).changes) },
|
||||
get modify() { return self.modify as any },
|
||||
}))
|
||||
|
||||
/**
|
||||
* Runs an effect when read failures occur.
|
||||
*
|
||||
* Applies to `get` and `changes` while leaving `modify` unchanged.
|
||||
*/
|
||||
export const tapErrorRead: {
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: NoInfer<ER>) => Effect.Effect<any, E2, R2>,
|
||||
): Lens<A, ER | E2, EW, RR | R2, RW>
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
f: (error: NoInfer<ER>) => Effect.Effect<any, E2, R2>,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW, RR | R2, RW>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: NoInfer<ER>) => Effect.Effect<any, E2, R2>,
|
||||
): Lens<A, ER | E2, EW, RR | R2, RW> => make({
|
||||
get get() { return Effect.tapError(self.get, f) },
|
||||
get changes() { return Stream.tapError(self.changes, f) },
|
||||
get modify() { return self.modify as any },
|
||||
}))
|
||||
|
||||
/**
|
||||
* Runs an effect when modify failures occur.
|
||||
*
|
||||
* Applies to the `modify` effect. Since `modify` may also fail with errors coming from the
|
||||
* user-supplied callback, the handler receives `unknown`.
|
||||
*/
|
||||
export const tapErrorWrite: {
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => Effect.Effect<any, E2, R2>,
|
||||
): Lens<A, ER, EW | E2, RR, RW | R2>
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
f: (error: unknown) => Effect.Effect<any, E2, R2>,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER, EW | E2, RR, RW | R2>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => Effect.Effect<any, E2, R2>,
|
||||
): Lens<A, ER, EW | E2, RR, RW | R2> => make({
|
||||
get get() { return self.get },
|
||||
get changes() { return self.changes },
|
||||
modify: <B, E1 = never, R1 = never>(
|
||||
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
|
||||
) => Effect.tapError(self.modify(g), f),
|
||||
}))
|
||||
|
||||
/**
|
||||
* Runs an effect when any `Lens` failure occurs.
|
||||
*
|
||||
* Applies to `get`, `changes`, and `modify`. Since `modify` may also fail with errors coming
|
||||
* from the user-supplied callback, the handler receives `unknown`.
|
||||
*/
|
||||
export const tapError: {
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => Effect.Effect<any, E2, R2>,
|
||||
): Lens<A, ER | E2, EW | E2, RR | R2, RW | R2>
|
||||
<A, ER, EW, RR, RW, E2, R2>(
|
||||
f: (error: unknown) => Effect.Effect<any, E2, R2>,
|
||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A, ER | E2, EW | E2, RR | R2, RW | R2>
|
||||
} = Function.dual(2, <A, ER, EW, RR, RW, E2, R2>(
|
||||
self: Lens<A, ER, EW, RR, RW>,
|
||||
f: (error: unknown) => Effect.Effect<any, E2, R2>,
|
||||
): Lens<A, ER | E2, EW | E2, RR | R2, RW | R2> => make({
|
||||
get get() { return Effect.tapError(self.get, f) },
|
||||
get changes() { return Stream.tapError(self.changes, f) },
|
||||
modify: <B, E1 = never, R1 = never>(
|
||||
g: (a: A) => Effect.Effect<readonly [B, A], E1, R1>
|
||||
) => Effect.tapError(self.modify(g), f),
|
||||
}))
|
||||
|
||||
|
||||
/**
|
||||
* Provides a `Context` to a `Lens`, removing it from both the read and write environments.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user