0.1.5 #4
@@ -36,13 +36,13 @@ Lens<
|
|||||||
|
|
||||||
### Creating a Lens
|
### Creating a Lens
|
||||||
|
|
||||||
#### From an exisiting type
|
#### From an existing type
|
||||||
We provide a few helpers to create Lenses from some Effect types:
|
We provide a few helpers to create Lenses from some Effect types:
|
||||||
```typescript
|
```typescript
|
||||||
// The ref is the data source
|
// The ref is the data source
|
||||||
const ref = yield* SubscriptionRef.make([12, 87, 69])
|
const ref = yield* SubscriptionRef.make([12, 87, 69])
|
||||||
|
|
||||||
// The lens acts as a proxy that allows reading, subscribing from and writing to that
|
// The lens acts as a proxy that allows reading, subscribing to and writing to that
|
||||||
// data source with a similar API to Effect's SubscriptionRef
|
// data source with a similar API to Effect's SubscriptionRef
|
||||||
const lens = Lens.fromSubscriptionRef(ref)
|
const lens = Lens.fromSubscriptionRef(ref)
|
||||||
// ^ Lens.Lens<number[], never, never, never, never>
|
// ^ Lens.Lens<number[], never, never, never, never>
|
||||||
@@ -54,7 +54,7 @@ yield* Lens.update(lens, Array.replace(1, 1664))
|
|||||||
|
|
||||||
Currently available:
|
Currently available:
|
||||||
- `fromSubscriptionRef`
|
- `fromSubscriptionRef`
|
||||||
- `fromSynchronizedRef` (note: since `SynchronizedRef` is not reactive (does not produces a stream of value changes), the resulting Lens' `changes` stream will only emit the current value of the lens when evaluated, and nothing else)
|
- `fromSynchronizedRef` (note: since `SynchronizedRef` is not reactive (does not produce a stream of value changes), the resulting Lens' `changes` stream will only emit the current value of the lens when evaluated, and nothing else)
|
||||||
|
|
||||||
More to come!
|
More to come!
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ Lens<{ readonly a: string, readonly b: number }, never, never, never, never>
|
|||||||
Lens<string, never, never, never, never>
|
Lens<string, never, never, never, never>
|
||||||
```
|
```
|
||||||
|
|
||||||
Focuses Lenses work just the same as a Lens that points directly to a data source and can be read, subscribed to or written to.
|
Focused Lenses work just the same as a Lens that points directly to a data source and can be read, subscribed to or written to.
|
||||||
|
|
||||||
Writing to them will properly update parent Lenses or data sources. Such updates can be performed in both a mutable or an immutable manner depending on your choice.
|
Writing to them will properly update parent Lenses or data sources. Such updates can be performed in both a mutable or an immutable manner depending on your choice.
|
||||||
|
|
||||||
@@ -137,29 +137,29 @@ const ref = yield* SubscriptionRef.make<{
|
|||||||
|
|
||||||
// \/ Lens<User, NoSuchElementException, NoSuchElementException, never, never>
|
// \/ Lens<User, NoSuchElementException, NoSuchElementException, never, never>
|
||||||
const jeanDupontLens = ref.pipe(
|
const jeanDupontLens = ref.pipe(
|
||||||
Lens.fromSubscriptionRef, // Creates a lens that proxies the ref
|
Lens.fromSubscriptionRef, // Creates a lens that proxies the ref
|
||||||
Lens.focusObjectField("users"), // Creates a focused lens that points to the users field
|
Lens.focusObjectOn("users"), // Creates a focused lens that points to the users field
|
||||||
Lens.focusArrayAt(0), // Creates a focused lens that points to the first entry of the user array
|
Lens.focusArrayAt(0), // Creates a focused lens that points to the first entry of the user array
|
||||||
)
|
)
|
||||||
// Reading or writing from this lense can fail with NoSuchElementException
|
// Reading or writing from this lens can fail with NoSuchElementException
|
||||||
// This is because of Lens.focusArrayAt(0), as reading and writing to an array is an unsafe operation
|
// This is because of Lens.focusArrayAt(0), as reading and writing to an array is an unsafe operation
|
||||||
|
|
||||||
const jeanDupont = yield* Lens.get(jeanDupontLens)
|
const jeanDupont = yield* Lens.get(jeanDupontLens)
|
||||||
|
|
||||||
yield* Lens.set(
|
yield* Lens.set(
|
||||||
// You can focus even further down
|
// You can focus even further down
|
||||||
Lens.focusObjectField(jeanDupontLens, "age"),
|
Lens.focusObjectOn(jeanDupontLens, "age"),
|
||||||
yield* DateTime.make("03/25/1970"),
|
yield* DateTime.make("03/25/1970"),
|
||||||
)
|
)
|
||||||
// Mutations with the parent state are performed immutably by default
|
// Mutations with the parent state are performed immutably by default
|
||||||
// unless you use a specific mutable transform such as 'focusMutableField'
|
// unless you use a specific mutable transform such as 'focusObjectOnWritable'
|
||||||
```
|
```
|
||||||
|
|
||||||
Currently available:
|
Currently available:
|
||||||
| Name | Description | Parent state mutation behavior | Notes |
|
| Name | Description | Parent state mutation behavior | Notes |
|
||||||
| - | - | - | - |
|
| - | - | - | - |
|
||||||
| `focusObjectField` | Focuses to the field of an object. Replaces the parent object immutably when writing to the focused field | Immutable | |
|
| `focusObjectOn` | Focuses to a field of an object. Replaces the parent object immutably when writing to the focused field | Immutable | |
|
||||||
| `focusObjectMutableField` | Focuses to the writable field of an object. Mutates the parent object in place via the writable field | Mutable | Type-safe: will not allow you to mutate `readonly` fields |
|
| `focusObjectOnWritable` | Focuses to a writable field of an object. Mutates the parent object in place via the writable field | Mutable | Type-safe: will not allow you to mutate `readonly` fields |
|
||||||
| `focusArrayAt` | Focuses to an indexed entry of an array. Replaces the parent array immutably when writing to the focused index | Immutable | |
|
| `focusArrayAt` | Focuses to an indexed entry of an array. Replaces the parent array immutably when writing to the focused index | Immutable | |
|
||||||
| `focusMutableArrayAt` | Focuses to an indexed entry of an array. Mutates the parent array in place at the focused index | Mutable | Type-safe: will not allow you to mutate `readonly` arrays |
|
| `focusMutableArrayAt` | Focuses to an indexed entry of an array. Mutates the parent array in place at the focused index | Mutable | Type-safe: will not allow you to mutate `readonly` arrays |
|
||||||
| `focusTupleAt` | Focuses to an indexed entry of a readonly tuple. Replaces the parent tuple immutably when writing to the focused index | Immutable | |
|
| `focusTupleAt` | Focuses to an indexed entry of a readonly tuple. Replaces the parent tuple immutably when writing to the focused index | Immutable | |
|
||||||
@@ -211,12 +211,12 @@ const someFunctionThatShouldOnlyHaveReadonlyAccessToTheState = (
|
|||||||
// Do whatever
|
// Do whatever
|
||||||
const usersCountSub = Subscribable.map(usersSub, a => a.length)
|
const usersCountSub = Subscribable.map(usersSub, a => a.length)
|
||||||
const users = yield* usersSub.get
|
const users = yield* usersSub.get
|
||||||
yield* Effect.forkScoped(Stream.runForEach(users.changes, ...))
|
yield* Effect.forkScoped(Stream.runForEach(usersSub.changes, ...))
|
||||||
})
|
})
|
||||||
|
|
||||||
const lens = ref.pipe(
|
const lens = ref.pipe(
|
||||||
Lens.fromSubscriptionRef,
|
Lens.fromSubscriptionRef,
|
||||||
Lens.focusObjectField("users"),
|
Lens.focusObjectOn("users"),
|
||||||
)
|
)
|
||||||
yield* someFunctionThatShouldOnlyHaveReadonlyAccessToTheState(lens)
|
yield* someFunctionThatShouldOnlyHaveReadonlyAccessToTheState(lens)
|
||||||
```
|
```
|
||||||
@@ -226,19 +226,19 @@ This library re-exports Effect's `Subscribable` module and adds a few transforms
|
|||||||
```typescript
|
```typescript
|
||||||
import { Subscribable } from "effect-lens"
|
import { Subscribable } from "effect-lens"
|
||||||
|
|
||||||
declare const sub: Subscribabe.Subscribable<readonly { name: string }[], never, never>
|
declare const sub: Subscribable.Subscribable<readonly { name: string }[], never, never>
|
||||||
|
|
||||||
// \/ Subscribabe.Subscribable<string, NoSuchElementException, never>
|
// \/ Subscribable.Subscribable<string, NoSuchElementException, never>
|
||||||
const nameSub = sub.pipe(
|
const nameSub = sub.pipe(
|
||||||
Subscribable.focusArrayAt(1),
|
Subscribable.focusArrayAt(1),
|
||||||
Subscribable.focusObjectField("name"),
|
Subscribable.focusObjectOn("name"),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Currently available:
|
Currently available:
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| - | - |
|
| - | - |
|
||||||
| `focusObjectField` | Focuses to the field of an object |
|
| `focusObjectOn` | Focuses to the field of an object |
|
||||||
| `focusArrayAt` | Focuses to an indexed entry of an array |
|
| `focusArrayAt` | Focuses to an indexed entry of an array |
|
||||||
| `focusTupleAt` | Focuses to an indexed entry of a tuple |
|
| `focusTupleAt` | Focuses to an indexed entry of a tuple |
|
||||||
| `focusChunkAt` | Focuses to an indexed entry of a `Chunk` |
|
| `focusChunkAt` | Focuses to an indexed entry of a `Chunk` |
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "effect-lens",
|
"name": "effect-lens",
|
||||||
"description": "An effectful Lens type to easily manage nested state",
|
"description": "An effectful Lens type to easily manage nested state",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"./README.md",
|
"./README.md",
|
||||||
@@ -21,10 +21,6 @@
|
|||||||
"types": "./dist/Lens.d.ts",
|
"types": "./dist/Lens.d.ts",
|
||||||
"default": "./dist/Lens.js"
|
"default": "./dist/Lens.js"
|
||||||
},
|
},
|
||||||
"./PropertyPath": {
|
|
||||||
"types": "./dist/PropertyPath.d.ts",
|
|
||||||
"default": "./dist/PropertyPath.js"
|
|
||||||
},
|
|
||||||
"./Subscribable": {
|
"./Subscribable": {
|
||||||
"types": "./dist/Subscribable.d.ts",
|
"types": "./dist/Subscribable.d.ts",
|
||||||
"default": "./dist/Subscribable.js"
|
"default": "./dist/Subscribable.js"
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ describe("Lens", () => {
|
|||||||
expect(result[1]).toEqual(Option.some(50)) // 100 / 2
|
expect(result[1]).toEqual(Option.some(50)) // 100 / 2
|
||||||
})
|
})
|
||||||
|
|
||||||
test("focusObjectField focuses a nested property without touching other fields", async () => {
|
test("focusObjectOn focuses a nested property without touching other fields", async () => {
|
||||||
const [initialCount, updatedState] = await Effect.runPromise(
|
const [initialCount, updatedState] = await Effect.runPromise(
|
||||||
Effect.flatMap(
|
Effect.flatMap(
|
||||||
SubscriptionRef.make({ count: 1, label: "original" }),
|
SubscriptionRef.make({ count: 1, label: "original" }),
|
||||||
parent => {
|
parent => {
|
||||||
const countLens = Lens.focusObjectField(Lens.fromSubscriptionRef(parent), "count")
|
const countLens = Lens.focusObjectOn(Lens.fromSubscriptionRef(parent), "count")
|
||||||
return Effect.flatMap(
|
return Effect.flatMap(
|
||||||
Lens.get(countLens),
|
Lens.get(countLens),
|
||||||
count => Effect.flatMap(
|
count => Effect.flatMap(
|
||||||
@@ -75,13 +75,13 @@ describe("Lens", () => {
|
|||||||
expect(updatedState).toEqual({ count: 6, label: "original" })
|
expect(updatedState).toEqual({ count: 6, label: "original" })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("focusObjectMutableField preserves the root identity when mutating in place", async () => {
|
test("focusObjectOnWritable preserves the root identity when mutating in place", async () => {
|
||||||
const original = { detail: "keep" }
|
const original = { detail: "keep" }
|
||||||
const updated = await Effect.runPromise(
|
const updated = await Effect.runPromise(
|
||||||
Effect.flatMap(
|
Effect.flatMap(
|
||||||
SubscriptionRef.make(original),
|
SubscriptionRef.make(original),
|
||||||
parent => {
|
parent => {
|
||||||
const detailLens = Lens.focusObjectMutableField(Lens.fromSubscriptionRef(parent), "detail")
|
const detailLens = Lens.focusObjectOnWritable(Lens.fromSubscriptionRef(parent), "detail")
|
||||||
return Effect.flatMap(
|
return Effect.flatMap(
|
||||||
Lens.set(detailLens, "mutated"),
|
Lens.set(detailLens, "mutated"),
|
||||||
() => parent.get,
|
() => parent.get,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ extends Subscribable.Subscribable<A, ER, RR> {
|
|||||||
* Internal `Lens` implementation.
|
* Internal `Lens` implementation.
|
||||||
*/
|
*/
|
||||||
export class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
|
export class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
|
||||||
extends Pipeable.Class() implements Lens<A, ER, EW, RR, RW> {
|
extends Pipeable.Class() implements Lens<A, ER, EW, RR, RW> {
|
||||||
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
|
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
|
||||||
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
|
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
|
||||||
readonly [LensTypeId]: LensTypeId = LensTypeId
|
readonly [LensTypeId]: LensTypeId = LensTypeId
|
||||||
@@ -273,7 +273,7 @@ export const mapStream: {
|
|||||||
/**
|
/**
|
||||||
* Narrows the focus to a field of an object. Replaces the object in an immutable fashion when written to.
|
* Narrows the focus to a field of an object. Replaces the object in an immutable fashion when written to.
|
||||||
*/
|
*/
|
||||||
export const focusObjectField: {
|
export const focusObjectOn: {
|
||||||
<A extends object, ER, EW, RR, RW, K extends keyof A>(
|
<A extends object, ER, EW, RR, RW, K extends keyof A>(
|
||||||
self: Lens<A, ER, EW, RR, RW>,
|
self: Lens<A, ER, EW, RR, RW>,
|
||||||
key: K,
|
key: K,
|
||||||
@@ -290,7 +290,7 @@ export const focusObjectField: {
|
|||||||
(a, b) => Object.setPrototypeOf({ ...a, [key]: b }, Object.getPrototypeOf(a)),
|
(a, b) => Object.setPrototypeOf({ ...a, [key]: b }, Object.getPrototypeOf(a)),
|
||||||
))
|
))
|
||||||
|
|
||||||
export declare namespace focusObjectMutableField {
|
export declare namespace focusObjectOnWritable {
|
||||||
export type WritableKeys<T> = {
|
export type WritableKeys<T> = {
|
||||||
[K in keyof T]-?: IfEquals<
|
[K in keyof T]-?: IfEquals<
|
||||||
{ [P in K]: T[K] },
|
{ [P in K]: T[K] },
|
||||||
@@ -304,17 +304,17 @@ export declare namespace focusObjectMutableField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Narrows the focus to a mutable field of an object. Mutates the object in place when written to.
|
* Narrows the focus to a writable field of an object. Mutates the object in place when written to.
|
||||||
*/
|
*/
|
||||||
export const focusObjectMutableField: {
|
export const focusObjectOnWritable: {
|
||||||
<A extends object, ER, EW, RR, RW, K extends focusObjectMutableField.WritableKeys<A>>(
|
<A extends object, ER, EW, RR, RW, K extends focusObjectOnWritable.WritableKeys<A>>(
|
||||||
self: Lens<A, ER, EW, RR, RW>,
|
self: Lens<A, ER, EW, RR, RW>,
|
||||||
key: K,
|
key: K,
|
||||||
): Lens<A[K], ER, EW, RR, RW>
|
): Lens<A[K], ER, EW, RR, RW>
|
||||||
<A extends object, ER, EW, RR, RW, K extends focusObjectMutableField.WritableKeys<A>>(
|
<A extends object, ER, EW, RR, RW, K extends focusObjectOnWritable.WritableKeys<A>>(
|
||||||
key: K,
|
key: K,
|
||||||
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A[K], ER, EW, RR, RW>
|
): (self: Lens<A, ER, EW, RR, RW>) => Lens<A[K], ER, EW, RR, RW>
|
||||||
} = Function.dual(2, <A extends object, ER, EW, RR, RW, K extends focusObjectMutableField.WritableKeys<A>>(
|
} = Function.dual(2, <A extends object, ER, EW, RR, RW, K extends focusObjectOnWritable.WritableKeys<A>>(
|
||||||
self: Lens<A, ER, EW, RR, RW>,
|
self: Lens<A, ER, EW, RR, RW>,
|
||||||
key: K,
|
key: K,
|
||||||
): Lens<A[K], ER, EW, RR, RW> => map(self, a => a[key], (a, b) => { a[key] = b; return a }))
|
): Lens<A[K], ER, EW, RR, RW> => map(self, a => a[key], (a, b) => { a[key] = b; return a }))
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { Option } from "effect"
|
|
||||||
import * as PropertyPath from "./PropertyPath.js"
|
|
||||||
|
|
||||||
|
|
||||||
describe("immutableSet with arrays", () => {
|
|
||||||
test("sets a top-level array element", () => {
|
|
||||||
const arr = [1, 2, 3]
|
|
||||||
const result = PropertyPath.immutableSet(arr, [1], 99)
|
|
||||||
expect(result).toEqual(Option.some([1, 99, 3]))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("does not mutate the original array", () => {
|
|
||||||
const arr = [1, 2, 3]
|
|
||||||
PropertyPath.immutableSet(arr, [0], 42)
|
|
||||||
expect(arr).toEqual([1, 2, 3])
|
|
||||||
})
|
|
||||||
|
|
||||||
test("sets the first element of an array", () => {
|
|
||||||
const arr = ["a", "b", "c"]
|
|
||||||
const result = PropertyPath.immutableSet(arr, [0], "z")
|
|
||||||
expect(result).toEqual(Option.some(["z", "b", "c"]))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("sets the last element of an array", () => {
|
|
||||||
const arr = [10, 20, 30]
|
|
||||||
const result = PropertyPath.immutableSet(arr, [2], 99)
|
|
||||||
expect(result).toEqual(Option.some([10, 20, 99]))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("sets a nested array element inside an object", () => {
|
|
||||||
const obj = { tags: ["foo", "bar", "baz"] }
|
|
||||||
const result = PropertyPath.immutableSet(obj, ["tags", 1], "qux")
|
|
||||||
expect(result).toEqual(Option.some({ tags: ["foo", "qux", "baz"] }))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("sets a deeply nested value inside an array of objects", () => {
|
|
||||||
const obj = { items: [{ name: "alice" }, { name: "bob" }] }
|
|
||||||
const result = PropertyPath.immutableSet(obj, ["items", 0, "name"], "charlie")
|
|
||||||
expect(result).toEqual(Option.some({ items: [{ name: "charlie" }, { name: "bob" }] }))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("sets a value in a nested array", () => {
|
|
||||||
const matrix = [[1, 2], [3, 4]]
|
|
||||||
const result = PropertyPath.immutableSet(matrix, [1, 0], 99)
|
|
||||||
expect(result).toEqual(Option.some([[1, 2], [99, 4]]))
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns Option.none() for an out-of-bounds index", () => {
|
|
||||||
const arr = [1, 2, 3]
|
|
||||||
const result = PropertyPath.immutableSet(arr, [5], 99)
|
|
||||||
expect(result).toEqual(Option.none())
|
|
||||||
})
|
|
||||||
|
|
||||||
test("returns Option.none() for a non-numeric key on an array", () => {
|
|
||||||
const arr = [1, 2, 3]
|
|
||||||
// @ts-expect-error intentionally wrong key type
|
|
||||||
const result = PropertyPath.immutableSet(arr, ["length"], 0)
|
|
||||||
expect(result).toEqual(Option.none())
|
|
||||||
})
|
|
||||||
|
|
||||||
test("empty path returns Option.some of the value itself", () => {
|
|
||||||
const arr = [1, 2, 3]
|
|
||||||
const result = PropertyPath.immutableSet(arr, [], [9, 9, 9] as any)
|
|
||||||
expect(result).toEqual(Option.some([9, 9, 9]))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { Array, Equivalence, Function, Option, Predicate } from "effect"
|
|
||||||
|
|
||||||
|
|
||||||
export type PropertyPath = readonly PropertyKey[]
|
|
||||||
|
|
||||||
type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
||||||
|
|
||||||
export type Paths<T, D extends number = 5, Seen = never> = readonly [] | (
|
|
||||||
D extends never ? readonly [] :
|
|
||||||
T extends Seen ? readonly [] :
|
|
||||||
T extends readonly any[] ? {
|
|
||||||
[K in keyof T as K extends number ? K : never]:
|
|
||||||
| readonly [K]
|
|
||||||
| readonly [K, ...Paths<T[K], Prev[D], Seen | T>]
|
|
||||||
} extends infer O
|
|
||||||
? O[keyof O]
|
|
||||||
: never
|
|
||||||
:
|
|
||||||
T extends object ? {
|
|
||||||
[K in keyof T as K extends string | number | symbol ? K : never]-?:
|
|
||||||
NonNullable<T[K]> extends infer V
|
|
||||||
? readonly [K] | readonly [K, ...Paths<V, Prev[D], Seen>]
|
|
||||||
: never
|
|
||||||
} extends infer O
|
|
||||||
? O[keyof O]
|
|
||||||
: never
|
|
||||||
:
|
|
||||||
never
|
|
||||||
)
|
|
||||||
|
|
||||||
export type ValueFromPath<T, P extends readonly any[]> = P extends readonly [infer Head, ...infer Tail]
|
|
||||||
? Head extends keyof T
|
|
||||||
? ValueFromPath<T[Head], Tail>
|
|
||||||
: T extends readonly any[]
|
|
||||||
? Head extends number
|
|
||||||
? ValueFromPath<T[number], Tail>
|
|
||||||
: never
|
|
||||||
: never
|
|
||||||
: T
|
|
||||||
|
|
||||||
|
|
||||||
export const equivalence: Equivalence.Equivalence<PropertyPath> = Equivalence.array(Equivalence.strict())
|
|
||||||
|
|
||||||
export const unsafeGet: {
|
|
||||||
<T, const P extends Paths<T>>(path: P): (self: T) => ValueFromPath<T, P>
|
|
||||||
<T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P>
|
|
||||||
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P> =>
|
|
||||||
path.reduce((acc: any, key: any) => acc?.[key], self)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const get: {
|
|
||||||
<T, const P extends Paths<T>>(path: P): (self: T) => Option.Option<ValueFromPath<T, P>>
|
|
||||||
<T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>>
|
|
||||||
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>> =>
|
|
||||||
path.reduce(
|
|
||||||
(acc: Option.Option<any>, key: any): Option.Option<any> => Option.isSome(acc)
|
|
||||||
? Predicate.hasProperty(acc.value, key)
|
|
||||||
? Option.some(acc.value[key])
|
|
||||||
: Option.none()
|
|
||||||
: acc,
|
|
||||||
|
|
||||||
Option.some(self),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const immutableSet: {
|
|
||||||
<T, const P extends Paths<T>>(path: P, value: ValueFromPath<T, P>): (self: T) => Option.Option<T>
|
|
||||||
<T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T>
|
|
||||||
} = Function.dual(3, <T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T> => {
|
|
||||||
const key = Array.head(path as PropertyPath)
|
|
||||||
if (Option.isNone(key))
|
|
||||||
return Option.some(value as T)
|
|
||||||
if (!Predicate.hasProperty(self, key.value))
|
|
||||||
return Option.none()
|
|
||||||
|
|
||||||
const child = immutableSet<any, any>(self[key.value], Option.getOrThrow(Array.tail(path as PropertyPath)), value)
|
|
||||||
if (Option.isNone(child))
|
|
||||||
return child
|
|
||||||
|
|
||||||
if (Array.isArray(self))
|
|
||||||
return typeof key.value === "number"
|
|
||||||
? Option.some([
|
|
||||||
...self.slice(0, key.value),
|
|
||||||
child.value,
|
|
||||||
...self.slice(key.value + 1),
|
|
||||||
] as T)
|
|
||||||
: Option.none()
|
|
||||||
|
|
||||||
if (typeof self === "object")
|
|
||||||
return Option.some(
|
|
||||||
Object.assign(
|
|
||||||
Object.create(Object.getPrototypeOf(self)),
|
|
||||||
{ ...self, [key.value]: child.value },
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Option.none()
|
|
||||||
})
|
|
||||||
@@ -42,7 +42,7 @@ export const mapOptionEffect: {
|
|||||||
/**
|
/**
|
||||||
* Narrows the focus to a field of an object.
|
* Narrows the focus to a field of an object.
|
||||||
*/
|
*/
|
||||||
export const focusObjectField: {
|
export const focusObjectOn: {
|
||||||
<A extends object, K extends keyof A, E, R>(
|
<A extends object, K extends keyof A, E, R>(
|
||||||
self: Subscribable.Subscribable<A, E, R>,
|
self: Subscribable.Subscribable<A, E, R>,
|
||||||
key: K,
|
key: K,
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
export * as Lens from "./Lens.js"
|
export * as Lens from "./Lens.js"
|
||||||
export * as PropertyPath from "./PropertyPath.js"
|
|
||||||
export * as Subscribable from "./Subscribable.js"
|
export * as Subscribable from "./Subscribable.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user