Skip to content

Data access

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 V
const namePath = path((u: User) => u.profile.name);
// Both-explicit form — use when T and V are needed separately
const 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 throw
namePath.get({} as User) // undefined — "profile" is missing

The 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 unchanged
updated.tags === user.tags // true — unaffected branches are shared

If 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"] — unchanged

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-renders
const value = useMemo(() => namePath.fn(state), [state]);

For template paths (each, deep), .fn returns V[] instead of V | undefined.

namePath.$ // "profile.name"
namePath.segments // ["profile", "name"]
namePath.length // 2

.segments is a readonly array of string | number. Numeric segments represent array indices.