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 = 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] } extends infer O ? O[keyof O] : never : T extends object ? { [K in keyof T as K extends string | number | symbol ? K : never]-?: NonNullable extends infer V ? readonly [K] | readonly [K, ...Paths] : never } extends infer O ? O[keyof O] : never : never ) export type ValueFromPath = P extends readonly [infer Head, ...infer Tail] ? Head extends keyof T ? ValueFromPath : T extends readonly any[] ? Head extends number ? ValueFromPath : never : never : T export const equivalence: Equivalence.Equivalence = Equivalence.array(Equivalence.strict()) export const unsafeGet: { >(path: P): (self: T) => ValueFromPath >(self: T, path: P): ValueFromPath } = Function.dual(2, >(self: T, path: P): ValueFromPath => path.reduce((acc: any, key: any) => acc?.[key], self) ) export const get: { >(path: P): (self: T) => Option.Option> >(self: T, path: P): Option.Option> } = Function.dual(2, >(self: T, path: P): Option.Option> => path.reduce( (acc: Option.Option, key: any): Option.Option => Option.isSome(acc) ? Predicate.hasProperty(acc.value, key) ? Option.some(acc.value[key]) : Option.none() : acc, Option.some(self), ) ) export const immutableSet: { >(path: P, value: ValueFromPath): (self: T) => Option.Option >(self: T, path: P, value: ValueFromPath): Option.Option } = Function.dual(3, >(self: T, path: P, value: ValueFromPath): Option.Option => { 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(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() })