Types
Path<T, V>
Section titled “Path<T, V>”A concrete path with no wildcards. T is the root type, V is the type at the leaf.
import type { Path } from "data-path";
function register<T>(fieldPath: Path<T, string>, value: string) { // fieldPath.$ is the dot-notation name // fieldPath.get(data) returns string | undefined}Path<T, V> conditionally includes .each() and .deep() — they are only present when V is not a primitive type. The full primitive set is string | number | boolean | bigint | symbol | null | undefined. This prevents calling .each() on a path that resolves to a leaf value, including paths whose resolved type is null or undefined (e.g. optional fields, nullable leaves).
const namePath = path((u: User) => u.profile.name); // V = string (primitive)// namePath.each ← not present in autocomplete
const profilePath = path((u: User) => u.profile); // V = { name: string }profilePath.each((p) => p.name) // ✓ V is non-primitiveThis guard is enforced at the type level — if .each() or .deep() are absent from autocomplete, the path already resolves to a scalar leaf value.
TemplatePath<T, V>
Section titled “TemplatePath<T, V>”A path containing one or more wildcards (* or **). Returned by .each() and .deep().
import type { TemplatePath } from "data-path";
function processAll<T, V>( tmpl: TemplatePath<T, V>, data: T, transform: (v: V) => V,): T { return tmpl.update(data, transform);}Key differences from Path:
.get(data)returnsV[]instead ofV | undefined.fnis(data: T) => V[].expand(data)returnsPath<T, V>[].to()and.merge()returnTemplatePath<T, U>rather thanPath<T, U>
BasePath<T, V>
Section titled “BasePath<T, V>”The shared base type for both Path and TemplatePath. Use it when your function accepts either kind:
import type { BasePath } from "data-path";
function getSegmentString<T, V>(p: BasePath<T, V>): string { return p.$;}BasePath does not include .each(), .deep(), .expand(), or the overridden get/fn signatures of TemplatePath.
ResolvedType<P>
Section titled “ResolvedType<P>”Extracts the leaf type V from a Path or TemplatePath:
import type { ResolvedType } from "data-path";
const agePath = path((u: User) => u.profile.age);type Age = ResolvedType<typeof agePath>; // number
const nameTmpl = path((u: User) => u.tags).each();type Tag = ResolvedType<typeof nameTmpl>; // stringReturns never for non-path types.
CollectionItem<V>
Section titled “CollectionItem<V>”Extracts the element type from an array or the value type from a Record:
import type { CollectionItem } from "data-path";
type A = CollectionItem<string[]>; // stringtype B = CollectionItem<Record<string, number>>; // numbertype C = CollectionItem<string>; // unknownUsed as the default value type in .each() — each() without an argument returns TemplatePath<T, CollectionItem<V>>.
Segment
Section titled “Segment”A single path segment. Most segments are either a string key or a number index:
import type { Segment } from "data-path";
const segments: Segment[] = ["profile", "tags", 0];Templates created by .each() and .deep() insert wildcard sentinels into their segment list. The sentinels are unique Symbol values (not the strings "*" / "**"), so legitimate object keys named "*" or "**" are preserved as literal segments and never reinterpreted as wildcards.
import { path, unsafePath, WILDCARD } from "data-path";
type Data = { items: { id: string }[] };const tmpl = path((d: Data) => d.items).each();tmpl.segments; // ["items", WILDCARD] — symbol, not the string "*"tmpl.$; // "items.*" — renders as "*" in dot-notation
type Bag = { a: Record<string, { b: number }> };const literal = unsafePath<Bag, number>("a.*.b");literal.segments; // ["a", "*", "b"] — pure strings; "*" is a literal keyliteral.get({ a: { "*": { b: 1 }, x: { b: 2 } } }); // 1WILDCARD and DEEP_WILDCARD are exported when you need to construct a template from a raw segment array (for example, to round-trip through JSON — see Templates).
MatchResult
Section titled “MatchResult”.match(other) returns MatchResult | null — a non-null result when the paths are structurally related, or null when they are unrelated (no shared prefix and no wildcard match). Always guard for null at the call site:
import type { MatchResult } from "data-path";
type MatchResult = { relation: "equals" | "parent" | "child" | "covers" | "covered-by";};
const result = namePath.match(otherPath);if (result === null) { // paths are unrelated} else { switch (result.relation) { /* ... */ }}| Relation | Meaning |
|---|---|
"equals" | Both paths are identical |
"parent" | this is a prefix of other |
"child" | other is a prefix of this |
"covers" | this contains a wildcard matching other |
"covered-by" | other contains a wildcard matching this |
null (return value, not a relation) | this and other share no prefix and no wildcard relationship |
ResolvablePath<T, V>
Section titled “ResolvablePath<T, V>”Accepted by all relational and algebra methods. Any of:
Path<T, V>orTemplatePath<T, V>- A lambda
(proxy: T) => V - An object
{ segments: readonly Segment[] }
This lets methods like .merge(), .subtract(), .equals(), and .startsWith() accept inline lambdas:
namePath.startsWith((u: User) => u.profile);namePath.subtract((u: User) => u.profile);