Skip to content

Runtime variables

The lambda passed to path() is executed once at creation time — the proxy records every property access and index operation. Because this is real JavaScript execution, any values that are in scope at that moment become part of the path.

import { path } from "data-path";
type AppData = { users: Array<{ name: string }> };
function userNamePath(index: number) {
return path((d: AppData) => d.users[index].name);
}
userNamePath(0).$ // "users.0.name"
userNamePath(5).$ // "users.5.name"

The path is fully resolved at the time path() is called. index is evaluated immediately, not lazily.

Any expression that evaluates to a string or number works as a key:

type Config = { settings: Record<string, string> };
const key = "theme";
const themePath = path((c: Config) => c.settings[key]);
themePath.$ // "settings.theme"
type Grid = { rows: Array<Array<{ value: number }>> };
function cellPath(row: number, col: number) {
return path((g: Grid) => g.rows[row][col].value);
}
cellPath(2, 3).$ // "rows.2.3.value"

In a .map() or similar loop, the lambda captures the current iteration value:

type Form = { users: Array<{ email: string }> };
const fieldPaths = [0, 1, 2].map((i) =>
path((f: Form) => f.users[i].email)
);
fieldPaths.map((p) => p.$) // ["users.0.email", "users.1.email", "users.2.email"]

The lambda runs exactly once. Expressions with side effects run only at path creation:

let count = 0;
const p = path((u: User) => u.tags[count++]);
count // 1 — incremented during creation
p.$ // "tags.0"

If the lambda depends on state that changes later, create a new path rather than mutating the captured value.

Runtime variables are the right choice when you know the specific index at creation time. Use .each() or .deep() when you need to operate across all items without knowing the count:

// Known index — runtime variable
const thirdItemPath = path((d: AppData) => d.users[2].name);
// All items — template wildcard
const allNamesPath = path((d: AppData) => d.users).each((u) => u.name);