import type { Predicate } from "effect"; import * as Array from "effect/Array"; import * as Effect from "effect/Effect"; import * as Record from "effect/Record"; type NestedObject = { [key: string]: T | NestedObject; }; export const mapLeaves = ( obj: NestedObject, leafRefinement: Predicate.Refinement, f: (value: T) => U, ): NestedObject => { const result: any = {}; for (const key in obj) { const value = obj[key]; if (leafRefinement(value)) { result[key] = f(value as T); } else { result[key] = mapLeaves(value as NestedObject, leafRefinement, f); } } return result; }; const collectBranchLeaves = ( obj: NestedObject, leafRefinement: Predicate.Refinement, path: string[] = [], ): { path: string[]; values: Record }[] => { const leaves = Record.filter(obj, leafRefinement) as Record; const hasLeaves = Record.keys(leaves).length > 0; const currentBranch = hasLeaves ? [{ path, values: leaves }] : []; const nestedBranches = Array.flatMap(Record.keys(obj), (key) => { const value = obj[key]; if (!leafRefinement(value) && typeof value === "object") { return collectBranchLeaves(value as NestedObject, leafRefinement, [ ...path, key, ]); } return []; }); return [...currentBranch, ...nestedBranches]; }; export const forEachBranchLeaves = ( obj: NestedObject, leafRefinement: Predicate.Refinement, f: (branchLeaves: { path: string[]; values: Record; }) => Effect.Effect, ): Effect.Effect => { const branchLeaves = collectBranchLeaves(obj, leafRefinement); return Effect.forEach(branchLeaves, f, { discard: true, }); }; export const setNestedProperty = ( obj: T, path: PropertyKey[], value: any, ): T => { if (path.length === 0) { return obj; } if (path.length === 1) { const key = path[0] as keyof T; return { ...obj, [key]: value }; } const [head, ...tail] = path; const key = head as keyof T; return { ...obj, [key]: setNestedProperty((obj as any)[key] ?? {}, tail, value), }; };