//#region src/tree.d.ts /** * A `Tree` is a recursive, hierarchical structure of branches. * * At a `required` node, a valid path must continue through a child branch. At an `optional` node, it may stop there or continue through a child branch. * * `null` marks the end of a branch. * * @example * const greetingTree = required({ * greetings: null, * hi: optional({ * $name: null, * }), * }) satisfies Tree */ export type Tree = OptionalTree | RequiredTree; export type OptionalTree = [`optional`, TreeBranches]; export type RequiredTree = [`required`, TreeBranches]; export type TreeBranches = Readonly<{ [key: string]: Tree | null; }>; /** * Create a tree node where a valid path must continue through a child branch. * * A `$...name` leaf branch consumes at least one string segment. Use {@link optional} to also permit stopping at this node. * * @param branches child branches mapped to their subtrees, or `null` for terminal branches * @returns a {@link RequiredTree} * * @example * const tree = required({ "$...segments": null }) * isTreePath(tree, []) // false * isTreePath(tree, ["one"]) // true * isTreePath(tree, ["one", "two"]) // true */ export declare function required(branches: B): [`required`, B]; /** * Create a tree node where a valid path may stop or continue through a child branch. * * Stopping here does not select a child branch. A selected `$...name` leaf still consumes at least one string segment. * * @param branches child branches mapped to their subtrees, or `null` for terminal branches * @returns an {@link OptionalTree} * * @example * const tree = optional({ "$...segments": null }) * isTreePath(tree, []) // true: stop at this node * isTreePath(tree, ["one"]) // true: select the rest branch * isTreePath(tree, ["one", "two"]) // true */ export declare function optional(branches: B): [`optional`, B]; //#endregion //#region src/utility-types.d.ts /** * Join an array of strings `Arr` together with a `Separator`. */ type Join = Arr extends [] ? `` : Arr extends [infer First extends string] ? First : Arr extends [infer First extends string, ...infer Rest extends string[]] ? `${First}${Separator}${Join}` | ([] extends Rest ? First : never) : string; /** * At instances of `Splitter` in split `Str` into an array of substrings. */ type Split = Str extends `${infer Head extends string}${Splitter}${infer Tail extends string}` ? [Head, ...Split] : Str extends `` ? [] : [Str]; /** * Expand capture elements in a string tuple into the string types they accept, preserving literal elements. * * Ordinary captures expand to `string & {}`. A terminal rest capture expands to `[string & {}, ...(string & {})[]]`. * * @typeParam Arr - The array of literal and capture elements to expand. * @typeParam CapturePrefix - The prefix identifying a capture; defaults to `$`. * @typeParam RestMarker - The marker immediately following `CapturePrefix` to identify a rest capture; defaults to `...`. * * @example * type Expanded = ExpandCaptures<["literal", "$name", "$...items"]> * // ["literal", string & {}, string & {}, ...(string & {})[]] * * @example * type Custom = ExpandCaptures<["literal", ":name", ":*items"], ":", "*"> * // ["literal", string & {}, string & {}, ...(string & {})[]] */ type ExpandCaptures = Arr extends [`${infer Head extends string}`, ...infer Tail extends string[]] ? Head extends `${CapturePrefix}${RestMarker}${string}` ? [string & {}, ...(string & {})[]] : Head extends `${CapturePrefix}${string}` ? [string & {}, ...ExpandCaptures] : [Head, ...ExpandCaptures] : []; type Flatten = { [K in keyof Record]: Record[K]; }; /** * Convert a union (`|`) to an intersection (`&` ). */ type UnionToIntersection = (Union extends any ? (x: Union) => void : never) extends ((x: infer Item) => void) ? Item : never; /** * Get the “last” element of a union (order is arbitrary). */ type LastInUnion = UnionToIntersection void : never> extends ((x: infer Last) => void) ? Last : never; /** * Convert a union to a tuple, order not guaranteed. */ type Distill> = [Union] extends [never] ? [] : [...Distill>, Last]; //#endregion //#region src/merge-trees.d.ts /** * For two `Tree`s `A` and `B`, deeply combine their structures into a single `Tree`. * * @example * MergeTrees< * typeof required({ * a: required({ bb: null }), * x: null, * }), * typeof required({ * a: required({ cc: null }), * y: null, * }) * > = * typeof required({ * a: required({ bb: null, cc: null }), * x: null, * y: null, * }) */ export type MergeTrees = [A[0] extends `required` ? B[0] extends `required` ? `required` : `optional` : `optional`, Flatten<{ [K in keyof A[1] | keyof B[1]]: K extends keyof A[1] ? K extends keyof B[1] ? A[1][K] extends Tree ? B[1][K] extends Tree ? Flatten> : A[1][K] : B[1][K] : B[1][K] extends Tree ? B[1][K] : A[1][K] : K extends keyof B[1] ? B[1][K] : never; }>]; /** * For two `Tree`s `A` and `B`, deeply combine their structures into a single `Tree`. * * @param treeA `A`, the first `Tree` to combine * @param treeB `B`, the second `Tree` to combine * @returns a {@link MergeTrees} type of the combined `Tree`s */ export declare function mergeTrees(treeA: A, treeB: B): MergeTrees & Tree; //#endregion //#region src/reduce-trees.d.ts /** * For a tuple of `Tree`s, combine them all into a single `Tree`. * * @example * ReduceTrees< * [ * typeof required({ x: null }), * typeof required({ y: null }), * typeof required({ z: null }), * ] * > = typeof required({ x: null, y: null, z: null }) */ export type ReduceTrees | Tree = [`required`, {}]> = Tuple extends [infer Head, ...infer Rest] ? Head extends Tree ? Rest extends Tree[] ? Flatten>> : never : never : Acc; /** * For any number of `Tree`s, combine them all into a single `Tree`. * * @param {...string} trees a tuple of `Tree`s to combine * @returns * a {@link ReduceTrees} type of the combined `Tree`s */ export declare function reduceTrees(...trees: T): ReduceTrees & Tree; //#endregion //#region src/tree-path-name.d.ts /** * For a `Tree`, the set of arrays that each hold a complete path within it. * * Wildcard segments are represented in their `$variable` form. * * @example * TreePathName< * typeof required({ * hello: optional({ * world: null, * $name: optional({ * good: required({ * morning: null * }) * }) * }), * }) * > = * | [`hello`] * | [`hello`, `world`] * | [`hello`, `$name`] * | [`hello`, `$name`, `good`, `morning`] */ type TreePathName = { [K in keyof T[1]]: T[0] extends `required` ? T[1][K] extends Tree ? [K, ...TreePathName] : [K] : (T[1][K] extends Tree ? [K, ...TreePathName] : [K]) | []; }[keyof T[1]]; /** * For a `Tree`, the set of arrays that each hold a path to any node within it. * * @example * TreeNodePathName< * typeof required({ * hello: optional({ * world: null, * $name: optional({ * good: required({ * morning: null * }) * }) * }), * }) * > = * | [] * | [`hello`] * | [`hello`, `world`] * | [`hello`, `$name`] * | [`hello`, `$name`, `good`] * | [`hello`, `$name`, `good`, `morning`] */ type TreeNodePathName = { [K in keyof T[1]]: (T[1][K] extends Tree ? [K, ...TreeNodePathName] : [K]) | []; }[keyof T[1]]; //#endregion //#region src/tree-map.d.ts /** * For a `Tree` `T`, a record from every {@link TreePathName} in `T` (joined together with separator `J`) to a value of type `D`. */ export type TreeMap = { [K in Join, J>]: D; }; /** * Make a record from each {@link TreePathName} in `T` (joined together with separator `J`) the same ${TreePathName} in array form. * * @param tree `T`, the source of truth for determining valid paths * @param separator (default `"/"`) the separator to use when joining each {@link TreePathName} * @returns a {@link TreeMap} of `T` */ export declare function flattenTree(tree: T, separator?: J): { [K in Join, J>]: Split; }; /** * @internal */ export declare function discoverBranches(basePrefix: string, basePath: string[], discoveredBranches: [pathName: string, path: string[]][], tree: Tree, separator?: J): [pathName: string, path: string[]][]; /** * Make a {@link TreeMap} from each {@link TreePathName} in `T` (joined together with separator `J`) to some data of type `D`. * * @param tree `T`, the source of truth for determining valid paths * @param mapper a function that maps each {@link TreePathName} in `T` to a value of type `D` * @param separator (default `"/"`) the separator to use when joining each {@link TreePathName} * @returns a {@link TreeMap} of `T` */ export declare function mapTree(tree: T, mapper: (path: TreePathName, joinedPath: Join, J>, tree: T) => D, separator?: J): TreeMap; export type TreeMapExhaustive = { [K in Join, J>]: P; }; //#endregion //#region src/tree-path.d.ts /** * For a `Tree`, the set of all paths through that tree. * * @example * TreePath< * typeof required({ * hello: optional({ * world: null, * $name: optional({ * good: required({ * morning: null * }) * }) * }), * }) * > = * | [`hello`] * | [`hello`, `world`] * | [`hello`, string & {}] * | [`hello`, string & {}, `good`, `morning`] */ export type TreePath = { [K in keyof T[1]]: (K extends `$...${string}` ? [string & {}, ...(string & {})[]] : T[1][K] extends Tree ? [K extends `$${string}` ? string & {} : K, ...TreePath] : [K extends `$${string}` ? string & {} : K]) | (T[0] extends `required` ? never : []); }[keyof T[1]]; /** * For a {@link TreePathName}, a record of the values captured by its wildcard segments. * * `$name` captures a string, and `$...name` captures one or more strings. * Fixed segments are omitted. Alternative path names produce a union of capture records. * * @example * TreePathCaptures<[`project`, `$name`, `$...paths`]> = * { name: string; paths: [string, ...string[]] } * * @example * TreePathCaptures<[`show`, `$name`] | [`add`, `$...paths`]> = * | { name: string } * | { paths: [string, ...string[]] } * * @example * TreePathCaptures<[`version`]> = {} */ export type TreePathCaptures = PathName extends unknown ? { [Segment in PathName[number] as Segment extends `$...${infer Name}` ? Name : Segment extends `$${infer Name}` ? Name : never]: Segment extends `$...${string}` ? [string, ...string[]] : string; } : never; /** * Against `Tree` `T`, validate whether a possible `maybePath` goes through it * * @param tree `T`, the source of truth for determining valid paths * @param maybePath the path to validate * @returns refinement for `maybePath` into a {@link TreePath} of `T` */ export declare function isTreePath(tree: T, maybePath: unknown[]): maybePath is TreePath; //#endregion export type { Distill, ExpandCaptures, Flatten, Join, LastInUnion, Split, TreeNodePathName, TreePathName, UnionToIntersection }; //# sourceMappingURL=treetrunks.d.ts.map