Data access
Creating paths
Section titled “Creating paths”Three forms, all producing the same Path<T, V> type:
import { path, unsafePath } from "data-path";
type User = { profile: { name: string }; tags: string[] };
// Annotation form — recommended, infers both T and Vconst namePath = path((u: User) => u.profile.name);
// Both-explicit form — use when T and V are needed separatelyconst tagsPath = path<User, string[]>((u) => u.tags);
// Root path — start here, extend with .to()const root = path<User>();const nameViaRoot = root.to((u) => u.profile.name);
// From a raw string — for dynamic contexts (API responses, Zod issues)const dynamic = unsafePath<User>("profile.name");Traverses each segment, returning undefined if any intermediate is null or undefined.
const user: User = { profile: { name: "Alice" }, tags: [] };
namePath.get(user) // "Alice"namePath.get(null) // undefined — no thrownamePath.get({} as User) // undefined — "profile" is missingThe return type is V | undefined — the path cannot guarantee the value exists at runtime.
Returns a structural clone with the value updated at the path. Every object along the path is cloned; everything else is shared.
const updated = namePath.set(user, "Bob");
updated.profile.name // "Bob"user.profile.name // "Alice" — original unchangedupdated.tags === user.tags // true — unaffected branches are sharedIf a segment points into an array, the array is cloned rather than the parent object:
type AppData = { items: string[] };const firstItemPath = path((d: AppData) => d.items[0]);const data: AppData = { items: ["a", "b"] };
const next = firstItemPath.set(data, "x");next.items // ["x", "b"]data.items // ["a", "b"] — unchangedupdate
Section titled “update”Reads the current value, passes it to the updater, then writes the result.
const incremented = scorePath.update(game, (n) => (n ?? 0) + 1);The updater receives V | undefined — handle the undefined case explicitly when the value might be absent.
On a plain Path, this is equivalent to path.set(data, updater(path.get(data))). On a TemplatePath the equivalence does not hold: .update invokes the updater once per matched item with that item’s value, then writes each result back to its own location. See Templates for the per-item form, or use .set(data, constant) if you want to broadcast a single value across every match.
A stable (data: T) => V | undefined function reference. It is assigned once in the constructor and does not change, so it is safe to use as a dependency or pass to array methods:
const users: User[] = [user1, user2, user3];const names = users.map(namePath.fn); // (string | undefined)[]
// React example — stable reference avoids unnecessary re-rendersconst value = useMemo(() => namePath.fn(state), [state]);For template paths (each, deep), .fn returns V[] instead of V | undefined.
String properties
Section titled “String properties”namePath.$ // "profile.name"namePath.segments // ["profile", "name"]namePath.length // 2.segments is a readonly array of string | number. Numeric segments represent array indices.
See also
Section titled “See also”- Templates — bulk reads and writes across collections
- Path Algebra — compose and decompose paths
- Runtime Variables — indices and closures inside lambdas