export type SearchTree = Record export type SearchFilter = (tree: SearchTree) => boolean export interface FindInTreeOptions { /** * A set of keys to search for in the tree. */ walkable?: Set /** * A set of keys to ignore when searching the tree. */ ignore?: Set /** * The maximum depth to search in the tree. * * @default 100 */ maxDepth?: number } export function findInTree(tree: SearchTree, filter: SearchFilter, opts?: FindInTreeOptions): any | undefined { type StackItem = [node: any, depth: number] const stack: StackItem[] = [[tree, 0]] const maxDepth = opts?.maxDepth ?? 100 while (stack.length > 0) { const [node, depth] = stack.pop()! if (depth > maxDepth || !node) continue if (filter(node)) return node const isArray = Array.isArray(node) const keys = isArray ? node : Object.keys(node) for (const key of keys) { const item = isArray ? key : node[key] if (typeof item !== 'object' || item == null) continue if (!isArray && opts?.walkable?.size && !opts.walkable?.has(key)) continue if (!isArray && opts?.ignore?.has(key)) continue stack.push([item, depth + 1]) } } }