UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

225 lines (224 loc) 12.3 kB
import type { ImmutableArray } from "./array.js"; import type { AbsolutePath } from "./path.js"; import type { Query } from "./query.js"; /** Set of valid props for an element. */ export type ElementProps = { readonly children?: Elements; }; /** * Element with a type, props, and optional key (compatible with `React.ReactElement`). * - Declared as a `type`, not an `interface`, so its implicit index signature lets it satisfy `Data` — `queryElements()` runs elements through `queryItems<T extends Data>`. */ export type Element<P extends ElementProps = ElementProps> = { readonly type: string | ((props: P) => Elements | null); readonly props: P; readonly key: string | null; readonly $$typeof?: symbol; }; /** Collection of elements (compatible with `React.ReactNode`). */ export type Elements<T extends Element = Element> = undefined | null | string | T | Iterable<Elements<T>>; /** Props for a tree element — must have a `tree-` prefixed type. */ export interface TreeElementProps extends ElementProps { /** * The primary identifier shown in menus, cards, and other listings. * - Always set. For files this is the basename (e.g. `"array"` from `array.ts`); for directories it's the directory name; * for documented symbols it's the declared name (e.g. `"getFirst"`). * - `key` is typically the slugified version of `name`. */ readonly name: string; /** * Optional visual-only override for `name`, set only when a confident source is available * (e.g. a markdown `<h1>`, a docblock title). * - Renderers should fall back to `name` when `title` is missing. */ readonly title?: string | undefined; /** * Short summary used for the page's `<meta name="description">` tag — not rendered as visible page body. * - Pages plumb this through `Meta.description`; `<Head>` emits the meta tag. * - For visible body content (rendered inside the page) use `content` instead. */ readonly description?: string | undefined; /** * Markup source string that should be rendered as the element's visible body content. * - Rendered via the `<Markup>` component (typically inside a `<Prose>` wrapper). * - For Markdown files this is the file's body (the title `# h1` is lifted into `title`); for TypeScript symbols this is the JSDoc description. */ readonly content?: string | undefined; /** Children of a tree element must be other tree elements. */ readonly children?: TreeElements | undefined; } /** * Element in a heirarchical tree. * - Has a `tree-` prefixed type string. * - Requires a string `key` prop (can be resolved to a path). * - Props can include `title`, `description`, and/or `content` to be useful. */ export interface TreeElement<P extends TreeElementProps = TreeElementProps> extends Element<P> { readonly key: string; readonly type: `tree-${string}`; } /** Collection of tree elements. */ export type TreeElements = Elements<TreeElement>; /** Props for a directory element. */ export interface DirectoryElementProps extends TreeElementProps { /** * Source location for the element — the absolute filesystem path it was extracted from. * - For directories this is the directory path; for files this is the file path. */ readonly source: AbsolutePath; } /** * Element representing a directory in a file tree. * - Content is absorbed from an index file (e.g. `README.md` or `INDEX.md`) if present. * - Children are the files and subdirectories within this directory. */ export interface DirectoryElement extends TreeElement<DirectoryElementProps> { readonly type: "tree-directory"; } /** Props for a file element. */ export interface FileElementProps extends TreeElementProps { /** * Source location for the element — the absolute filesystem path it was extracted from. * - For directories this is the directory path; for files this is the file path. */ readonly source: AbsolutePath; } /** * Element representing a file in a file tree. * - For TypeScript files, children are the exported code symbols. * - For Markdown files, children are typically empty (content is the parsed markdown). */ export interface FileElement extends TreeElement<FileElementProps> { readonly type: "tree-file"; } /** A single parameter for a documented code symbol. */ export interface DocumentationParam { readonly name: string; /** Type expression (e.g. `"string"`, `"number"`); renderers default to `"unknown"` when missing. */ readonly type?: string | undefined; readonly description?: string | undefined; readonly optional?: boolean | undefined; } /** A single `@returns` entry — multiple allowed to document union return types separately. */ export interface DocumentationReturn { /** Type expression for the return value; renderers default to `"unknown"` when missing. */ readonly type?: string | undefined; readonly description?: string | undefined; } /** A single `@throws` entry — multiple allowed to document distinct error types. */ export interface DocumentationThrow { /** Type expression for the thrown value; renderers default to `"unknown"` when missing. */ readonly type?: string | undefined; readonly description?: string | undefined; } /** A single `@example` entry — the example text/code block. */ export interface DocumentationExample { readonly description?: string | undefined; } /** * Props for a documented code symbol — a single shape for any kind of code element. * - `kind` distinguishes the symbol category (e.g. `"function"`, `"class"`, `"property"`, or any string). * - All props are optional — not every kind uses every prop (e.g. `returns` only makes sense for functions). * - `signatures` is an array so overloaded function/method declarations can each contribute their own signature to the same documentation element. */ export interface DocumentationElementProps extends TreeElementProps { readonly kind?: string | undefined; readonly signatures?: ImmutableArray<string> | undefined; readonly params?: ImmutableArray<DocumentationParam> | undefined; readonly returns?: ImmutableArray<DocumentationReturn> | undefined; readonly throws?: ImmutableArray<DocumentationThrow> | undefined; readonly examples?: ImmutableArray<DocumentationExample> | undefined; readonly extends?: string | undefined; readonly implements?: ImmutableArray<string> | undefined; } /** * Element representing a documented code symbol. * - The `kind` prop distinguishes specific symbol types (function, class, property, etc.) without baking the list in. */ export interface DocumentationElement extends TreeElement<DocumentationElementProps> { readonly type: "tree-documentation"; } declare module "react" { namespace JSX { interface IntrinsicElements { "tree-directory": DirectoryElementProps; "tree-file": FileElementProps; "tree-documentation": DocumentationElementProps; } } } /** Is an unknown value an element? */ export declare function isElement(value: unknown): value is Element; /** Is an unknown value a collection of elements? */ export declare function isElements(value: unknown): value is Elements; /** * Strip all tags from elements to produce a plain text string. * - A `<br>` element becomes a newline (`\n`) — matching DOM `innerText`, so words either side of a line break don't fuse together. * * @param elements An element, a plain string, or null/undefined (or an array of those things). * @returns The combined string made from the elements. * * @example `- Item with *strong*\n- Item with _em_` becomes `Item with strong Item with em` */ export declare function getElementText(elements: Elements): string; /** * Walk an `Elements` value into a flat iterable of `Element` objects. * - Accepts any shape the `Elements` union allows: a single element, a (possibly deeply nested) iterable, `null`, `undefined`, or a string (strings are skipped — there's no element to yield). * - Recurses through *iterable nesting* only (e.g. `[[a, b], c]` flattens to `a, b, c`); it does NOT descend into an element's own `props.children`. Walking deeper is the consumer's job. * * The point of this helper is to remove the "is it one element, a list, undefined, or some nested thing" branching from every consumer that needs to dispatch over elements — pass it in, get a clean flat iterable out. */ export declare function walkElements<T extends Element>(elements: Elements<T>): Iterable<T>; export declare function walkElements(elements: Elements): Iterable<Element>; /** * Filter elements yielded by `walkElements()` using a `Query<Element>` object. * - Supports any property query (e.g. `{ type: "tree-file" }`, `{ type: ["tree-file", "tree-directory"] }`), sorting, limiting — anything `queryItems()` accepts. */ export declare function queryElements(elements: Elements, query: Query<Element>): Iterable<Element>; /** Filter elements yielded by `walkElements()` using a match function. */ export declare function filterElements(elements: Elements, match: (element: Element) => boolean): Iterable<Element>; /** * Resolve an element in a tree by walking a sequence of names from `root`. * - The `root` element's own name is never matched against path segments — it's the container, not a step in the path. * - A child's `name` may contain `/` separators, in which case it matches multiple consecutive path segments * (e.g. a module named `"util/string"` matches the segments `["util", "string"]`). * - If `path` is empty, returns `root` itself. * - Returns `undefined` if no descendant matches at any level. * * Splitting the path: * - We accept a raw `Segments` array, so callers can join paths later however they wish. * - Element paths have no canonical string representation so we use `Segments` instead. * - To split the names in `a.b.c` dotted data format use `mapItems(getElementPaths(root), splitDataPath)` * - To split the names in `/a/b/c` absolute path format use `mapItems(getElementPaths(root), splitAbsolutePath)` * * @param root The root element to walk from. Its own `name` is treated as a label, not a path segment. * @param path An array of path segments naming descendants of `root`. * * @example resolveElementPath(root, ["util", "array"]) // Element with name "array" inside child with name "util" * @example resolveElementPath(root, ["util", "string"]) // Module child with composite name "util/string" * @example resolveElementPath(root, []) // `root` itself */ export declare function resolveElementPath(root: TreeElement, path: readonly string[]): TreeElement | undefined; /** * Deeply iterate a tree from `root` and yield path segments for each reachable element. * - Yields `[]` for `root` itself. * - Yields `[name]` for each immediate child, `[name, name]` for grandchildren, etc. * - The `root` element's own name never appears in any yielded path — it's the container. * - Children with no `name` prop are skipped (and their descendants are not yielded). * * Joining the paths: * - We return a `Segments` array for each element, so callers can join paths later however they wish. * - Element paths have no canonical string representation so we use `Segments` instead. * - To join the names in `a.b.c` dotted data format use `mapItems(getElementPaths(root), joinDataPath)` * - To join the names in `/a/b/c` absolute path format use `mapItems(getElementPaths(root), p => joinPath("/", p))` * * @param root The root element to walk from. Its own `name` is treated as a label, not a path segment. * @param depth Controls how many levels of children to recurse into (defaults to infinite depth). * - `depth=0` yields only `[]` (the root itself). * * @returns Iterable set of path segment arrays, each representing one descendant (or the root). */ export declare function getElementPaths(root: TreeElement, depth?: number): Iterable<readonly string[]>; /** Combine two `Elements`, preserving both if both are set. */ export declare function mergeElements<T extends Element>(a: Elements<T>, b: Elements<T>): Elements<T>; export declare function mergeElements(a: Elements, b: Elements): Elements;