This commit is contained in:
@@ -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()
|
||||
})
|
||||
@@ -1,3 +1,2 @@
|
||||
export * as Lens from "./Lens.js"
|
||||
export * as PropertyPath from "./PropertyPath.js"
|
||||
export * as Subscribable from "./Subscribable.js"
|
||||
|
||||
Reference in New Issue
Block a user