Relational
Relational methods are useful for permissions checks, validation mapping, and UI logic where you need to know how two paths relate — whether one is a prefix of another, whether they overlap, or what structural relationship they have.
Mental model
Section titled “Mental model”Think of every path as a location in the data tree. A shorter path like profile covers a wider subtree; a deeper path like profile.name points at a specific spot inside that subtree. startsWith and covers are direction-reversed views of the same containment:
deeperPath.startsWith(widerPath)— does the deeper location fall under the wider one?widerPath.covers(deeperPath)— does the wider location contain the deeper one?
(covers is not like Array.prototype.includes; it asks about data-tree containment, not element membership.)
startsWith
Section titled “startsWith”Returns true if the path begins with every segment of other.
import { path } from "data-path";
type User = { profile: { name: string; age: number } };
const namePath = path((u: User) => u.profile.name);const profilePath = path((u: User) => u.profile);
namePath.startsWith(profilePath) // true — "profile.name" starts with "profile"profilePath.startsWith(namePath) // falseThe argument can be a Path, a lambda, or a { segments } object.
covers
Section titled “covers”Returns true if this path’s location covers other — i.e. this path is a prefix of other (the inverse direction of startsWith).
profilePath.covers(namePath) // true — "profile" covers "profile.name"namePath.covers(profilePath) // falseWildcards and direction
Section titled “Wildcards and direction”When one path contains wildcards (from .each() or .deep()), use whichever side reads most naturally:
| Called on | Argument | Returns true when |
|---|---|---|
| template | .covers(concrete) | template’s pattern covers the concrete path |
| concrete | .startsWith(template) | concrete falls under the template prefix |
interface R { items: Array<{ name: string }> }
const allNames = path((r: R) => r.items).each((i: { name: string }) => i.name);const one = path((r: R) => r.items[0].name);
allNames.covers(one) // true — the template covers this concrete pathone.startsWith(allNames) // true — the concrete falls under the template prefixThe two are logically equivalent — pick the form that reads naturally for your use case.
For ** (deep wildcard), any number of intermediate segments can match:
interface Root { tree: { a: { b: { c: string } } } }
const deep = path((r: Root) => r.tree).deep(); // ["tree", "**"]const leaf = path((r: Root) => r.tree.a.b.c); // ["tree", "a", "b", "c"]
leaf.startsWith(deep) // true — "**" matches "a.b.c"deep.covers(leaf) // trueequals
Section titled “equals”Returns true if both paths have identical segments.
const p1 = path((u: User) => u.profile.name);const p2 = path((u: User) => u.profile.name);
p1.equals(p2) // truep1.equals(profilePath) // falseReturns a MatchResult describing the structural relationship, or null if there is none.
type MatchResult = { relation: "equals" | "parent" | "child" | "covers" | "covered-by";};namePath.match(profilePath) // { relation: "child" } — name is under profileprofilePath.match(namePath) // { relation: "parent" } — profile contains namenamePath.match(namePath) // { relation: "equals" }
const unrelated = path((u: User) => u.profile.age);namePath.match(unrelated) // null — no relationshipRelation meanings:
| 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 that matches other |
"covered-by" | other contains a wildcard that matches this |
Practical example — permission guard
Section titled “Practical example — permission guard”type Store = { users: User[]; settings: { theme: string } };
const settingsPath = path((s: Store) => s.settings);
function canWrite(targetPath: Path<Store, unknown>): boolean { // Allow writes only outside the settings subtree return !targetPath.startsWith(settingsPath);}
canWrite(path((s: Store) => s.users[0].name)) // truecanWrite(path((s: Store) => s.settings.theme)) // falseSee also
Section titled “See also”- Path Algebra —
merge,subtract,slice - Types —
MatchResultandResolvablePath