0.2.0 #5
@@ -1,11 +1,128 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
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"
|
import * as Lens from "./Lens.js"
|
||||||
|
|
||||||
|
|
||||||
describe("Lens", () => {
|
describe("Lens", () => {
|
||||||
class Offset extends Context.Tag("Offset")<Offset, { readonly value: number }>() {}
|
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 () => {
|
test("mapOption transforms Some values and preserves None", async () => {
|
||||||
const result = await Effect.runPromise(
|
const result = await Effect.runPromise(
|
||||||
Effect.flatMap(
|
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.
|
* Provides a `Context` to a `Lens`, removing it from both the read and write environments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user