data-path 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,319 +1,376 @@
1
+ /**
2
+ * Sentinel for a single-level wildcard segment (`each`).
3
+ *
4
+ * Stored as a unique Symbol — NOT the string `"*"` — so that a legitimate
5
+ * object key named `"*"` is preserved as a literal segment and never
6
+ * reinterpreted as a wildcard by `.get`, `.expand`, `.covers`, `.match`, etc.
7
+ *
8
+ * Renders as `"*"` in `toString()` / `.$` for dot-notation compatibility.
9
+ */
10
+ declare const WILDCARD: unique symbol;
11
+ /**
12
+ * Sentinel for a deep wildcard segment (`deep`).
13
+ *
14
+ * Same rationale as {@link WILDCARD}: stored as a unique Symbol so the
15
+ * literal string `"**"` remains a valid (literal) object key.
16
+ *
17
+ * Renders as `"**"` in `toString()` / `.$`.
18
+ */
19
+ declare const DEEP_WILDCARD: unique symbol;
20
+
1
21
  /**
2
22
  * Core type definitions for data-path.
3
- * @see spec/idea.md
4
23
  */
5
- /** A path segment: string key or numeric index */
6
- type Segment = string | number;
7
- /** Relation returned by .match() */
8
- type MatchRelation = "includes" | "included-by" | "equals" | "parent" | "child" | null;
9
- /** Result of .match()bidirectional relationship + optional params */
24
+
25
+ /**
26
+ * A path segment.
27
+ *
28
+ * - `string`object key
29
+ * - `number` — array index
30
+ * - `typeof WILDCARD` — single-level wildcard sentinel (inserted by `.each()`)
31
+ * - `typeof DEEP_WILDCARD` — deep wildcard sentinel (inserted by `.deep()`)
32
+ *
33
+ * The wildcard sentinels are unique Symbols (not the strings `"*"` / `"**"`),
34
+ * so legitimate object keys named `"*"` or `"**"` are preserved as literal
35
+ * string segments and never reinterpreted as wildcards by any method.
36
+ *
37
+ * The sentinels render as `"*"` / `"**"` in `toString()` / `.$` so dot-notation
38
+ * output and form-library bindings are unaffected.
39
+ */
40
+ type Segment = string | number | typeof WILDCARD | typeof DEEP_WILDCARD;
41
+ /**
42
+ * The structural relation returned by `.match()`.
43
+ *
44
+ * Semantics when calling `a.match(b)`:
45
+ * - `"parent"` — `a` is a prefix of `b` (a is the parent, b is deeper)
46
+ * - `"child"` — `b` is a prefix of `a` (b is the parent, a is deeper)
47
+ * - `"equals"` — `a` and `b` are identical
48
+ * - `"covers"` — `a` (with wildcards) covers `b` as a concrete match
49
+ * - `"covered-by"` — `b` (with wildcards) covers `a` as a concrete match
50
+ */
51
+ type MatchRelation = "covers" | "covered-by" | "equals" | "parent" | "child";
52
+ /** Result of .match() — relation only; params are reserved for named-wildcard support */
10
53
  interface MatchResult {
11
54
  relation: MatchRelation;
12
- params?: Record<string, string>;
13
55
  }
14
- /** Resolved type at the end of a path (leaf value type) */
15
- type ResolvedType<_T, _P extends string> = unknown;
16
- /** Deep-reachable types for .deep() leaf parameter (enables IDE autocomplete) */
17
- type DeepReachable<T> = T;
18
56
  /**
19
- * Extracts the item type from a collection (Array or Record) so that traversal methods
20
- * (like `.each()`) know what type of item they are iterating over.
57
+ * Extracts the resolved value type from a path object.
21
58
  *
22
- * @template V The collection type (e.g., `string[]` or `Record<string, number>`)
59
+ * @example
60
+ * const agePath = path<User>(p => p.profile.age);
61
+ * type Age = ResolvedType<typeof agePath>; // number
62
+ */
63
+ type ResolvedType<P> = P extends {
64
+ get(data: any): (infer V)[];
65
+ } ? V : P extends BasePath<any, infer V> ? V : never;
66
+ /**
67
+ * Extracts the item type from a collection (Array or Record).
68
+ * Used by `.each()` to infer the traversal target.
23
69
  */
24
70
  type CollectionItem<V> = V extends ReadonlyArray<infer U> ? U : V extends Record<PropertyKey, infer U> ? U : unknown;
25
- /** Primitives that cannot have traversal methods called on them */
71
+ /** Primitive types that cannot be traversed `.each()` and `.deep()` are hidden when V extends Primitive */
26
72
  type Primitive = string | number | boolean | symbol | bigint | null | undefined;
27
- /** Expression type for path construction — receives proxy, returns any (path is inferred from access) */
73
+ /** Lambda used to build a path — receives a proxy typed as T, infers the path from property access */
28
74
  type PathExpression<T, R = unknown> = (proxy: T) => R;
29
75
  /**
30
- * Represents the various forms a path can take when provided as an input argument.
31
- * This enables API flexibility, allowing methods (like `merge`, `match`, `startsWith`)
32
- * to accept an existing path object, a raw segments object, or a lambda expression.
33
- *
34
- * @template T The root data type.
35
- * @template V The resolved value type at the end of the path.
76
+ * Flexible path argument accepts an existing path, a `{segments}` shape, or a lambda expression.
77
+ * Used by all methods that accept a path as an argument.
36
78
  */
37
- type ResolvablePath<T, V = unknown> = BasePath<T, V, string> | {
79
+ type ResolvablePath<T, V = unknown> = BasePath<T, V> | {
38
80
  segments: readonly Segment[];
39
81
  } | PathExpression<T, V>;
40
82
  /**
41
- * Extracted methods for traversing into collections or deep structures.
42
- * This is separated from `BasePath` so that these methods can be conditionally
43
- * excluded from the type system when a path points to a primitive value
44
- * (since primitives cannot be traversed).
83
+ * Traversal methods only present when V is not a primitive type.
84
+ * Conditionally excluded by the Path and TemplatePath types.
45
85
  */
46
86
  interface TraversablePathMethods<T, V> {
47
87
  /**
48
- * Traverses into a collection (Array or Record) to operate on each item.
88
+ * Traverses into a collection (Array or Record), inserting a `*` wildcard.
89
+ * Returns a TemplatePath that matches every item.
49
90
  *
50
91
  * @example
51
- * const users = path<Root>().users;
52
- * const userNames = users.each(u => u.name); // Path matches all names
92
+ * path<Root>().users.each(u => u.name) // TemplatePath — all names
93
+ * path<Root>().users.each() // TemplatePath all items
53
94
  */
54
- each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U, string>;
95
+ each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U>;
55
96
  /**
56
- * Traverses deeply into a structure, matching any nested property.
97
+ * Traverses deeply into a structure, inserting a `**` wildcard.
98
+ * Returns a TemplatePath that matches the given property at any nesting depth.
57
99
  *
58
100
  * @example
59
- * const root = path<Root>();
60
- * const allIds = root.deep(node => node.id); // Path matches any 'id' at any depth
101
+ * path<Root>().tree.deep(node => node.id) // TemplatePath — any nested 'id'
102
+ * path<Root>().tree.deep() // TemplatePath every descendant node
61
103
  */
62
- deep<U = DeepReachable<V>>(expr?: (leaf: DeepReachable<V>) => U): TemplatePath<T, U, string>;
104
+ deep<U = V>(expr?: (leaf: V) => U): TemplatePath<T, U>;
63
105
  }
64
106
  /**
65
- * The foundational structure for all path objects (both standard and template paths).
66
- * Contains common properties and operations including value extraction/mutation,
67
- * relational algebra (comparisons), and structural manipulation.
107
+ * The foundational interface shared by both Path and TemplatePath.
68
108
  *
69
- * @template T Root data type the path operates on.
70
- * @template V The expected value type that the path resolves to.
71
- * @template _P The string representation of the path (optional/unused in runtime, but useful for type-level strings).
109
+ * @template T Root data type the path operates on
110
+ * @template V Resolved value type at the end of the path
72
111
  */
73
- interface BasePath<T = unknown, V = unknown, _P extends string = string> {
74
- /**
75
- * The array of segments (string keys or numeric indices) that make up this path.
76
- */
112
+ interface BasePath<T = unknown, V = unknown> {
113
+ /** Ordered array of string keys and numeric indices that compose this path. */
77
114
  readonly segments: readonly Segment[];
78
- /**
79
- * The number of segments in this path.
80
- */
115
+ /** Number of segments in this path. */
81
116
  readonly length: number;
82
117
  /**
83
- * The string representation of the path (e.g. "users.0.name").
84
- * Useful for binding paths to form libraries or UI components.
118
+ * Dot-notation string representation (e.g. `"users.0.name"`).
119
+ * Convenient for binding paths to form libraries.
85
120
  *
86
121
  * @example
87
- * path<Root>().users[0].name.$; // "users.0.name"
88
- */
89
- readonly $: _P;
90
- /**
91
- * Returns the string representation of the path (e.g. "users.0.name").
92
- *
93
- * @example
94
- * path<Root>().users[0].name.toString(); // "users.0.name"
122
+ * path<Root>(r => r.users[0].name).$ // "users.0.name"
95
123
  */
124
+ readonly $: string;
125
+ /** Returns the dot-notation string representation. */
96
126
  toString(): string;
97
127
  /**
98
- * Extracts the value at this path from the given data object.
99
- * Safely handles missing intermediate properties by returning `undefined` instead of throwing an error.
128
+ * Extracts the value at this path from a data object.
129
+ * Returns `undefined` rather than throwing when any intermediate segment is missing.
100
130
  *
101
131
  * @example
102
- * const namePath = path<User>().name;
103
- * const name = namePath.get({ name: "Alice" }); // "Alice"
132
+ * path<User>(u => u.profile.name).get(user) // string | undefined
104
133
  */
105
- get(data: T): V;
134
+ get(data: T): V | undefined;
106
135
  /**
107
- * Returns an accessor function that extracts the value at this path from the given data object.
108
- * Useful for array methods like `.map()` or `.filter()`.
136
+ * Pre-bound accessor function. Useful for array higher-order methods.
109
137
  *
110
138
  * @example
111
- * const names = users.map(path<User>().name.fn);
139
+ * users.map(path<User>(u => u.name).fn) // string | undefined[]
112
140
  */
113
- readonly fn: (data: T) => V;
141
+ readonly fn: (data: T) => V | undefined;
114
142
  /**
115
- * Sets the value at this path in the given data object, returning a new updated object (immutable).
116
- * If intermediate properties are missing, they are automatically created as objects or arrays
117
- * depending on the segment types (numeric keys become arrays).
143
+ * Immutably sets the value at this path, returning a structurally-cloned object.
144
+ * Missing intermediates are created automatically:
145
+ * numeric next-segment array, string next-segment object.
118
146
  *
119
147
  * @example
120
- * const namePath = path<User>().name;
121
- * const updatedUser = namePath.set({ name: "Alice" }, "Bob"); // { name: "Bob" }
148
+ * path<User>(u => u.name).set(user, "Alice")
122
149
  */
123
150
  set(data: T, value: V): T;
124
151
  /**
125
- * Checks if this path starts with the segments of another path.
152
+ * Reads the current value, passes it to `updater`, and writes the result back immutably.
153
+ * Combines `.get()` + `.set()` in a single expression.
154
+ * On a `TemplatePath`, `updater` is called once per expanded match (per-item transform).
126
155
  *
127
156
  * @example
128
- * const a = path<Root>().users[0].name;
129
- * const b = path<Root>().users;
130
- * a.startsWith(b); // true
157
+ * namePath.update(user, name => (name ?? "").toUpperCase())
131
158
  */
132
- startsWith(other: ResolvablePath<T>): boolean;
159
+ update(data: T, updater: (current: V | undefined) => V): T;
133
160
  /**
134
- * Checks if this path encompasses the segments of another path (i.e., this path is a prefix of the other).
161
+ * Returns the parent path (all segments except the last), or `null` for a root/empty path.
162
+ * Value type becomes `unknown` — use a typed path expression if the parent type is needed.
135
163
  *
136
164
  * @example
137
- * const a = path<Root>().users;
138
- * const b = path<Root>().users[0].name;
139
- * a.includes(b); // true
165
+ * path<User>(u => u.profile.name).parent()?.$ // "profile"
166
+ * path<User>().parent() // null
140
167
  */
141
- includes(other: ResolvablePath<T>): boolean;
168
+ parent(): Path<T, unknown> | null;
142
169
  /**
143
- * Checks if this path is exactly equal to another path.
170
+ * Returns `true` if this path's segments begin with all segments of `other`
171
+ * (i.e. `other` is a prefix of `this`). Supports wildcard segments.
172
+ */
173
+ startsWith(other: ResolvablePath<T>): boolean;
174
+ /**
175
+ * Returns `true` if this path's domain covers `other` — i.e. this path's segments
176
+ * are a prefix of `other`'s segments (or match `other` via wildcards). The inverse
177
+ * direction of {@link startsWith}.
178
+ *
179
+ * NOTE: this is NOT analogous to `Array.prototype.includes` / `String.prototype.includes`.
180
+ * Think "covers a location in the data tree", not "contains as an element".
144
181
  *
145
182
  * @example
146
- * const a = path<Root>().users;
147
- * const b = path<Root>().users;
148
- * a.equals(b); // true
183
+ * profilePath.covers(namePath) // true — "profile" covers "profile.name"
184
+ * namePath.covers(profilePath) // false
149
185
  */
186
+ covers(other: ResolvablePath<T>): boolean;
187
+ /** Returns `true` if this path is segment-by-segment identical to `other`. */
150
188
  equals(other: ResolvablePath<T>): boolean;
151
189
  /**
152
- * Matches this path against another path, returning their relationship.
190
+ * Returns the structural relationship between this path and `other`, or `null` when unrelated.
191
+ *
192
+ * `"parent"` means **this** path is the parent (shorter prefix); `"child"` means **this** is deeper.
153
193
  *
154
194
  * @example
155
- * const a = path<Root>().users[0];
156
- * const b = path<Root>().users;
157
- * a.match(b); // { relation: 'child', params: {} }
195
+ * profilePath.match(namePath) // { relation: "parent" } — profilePath IS the parent
196
+ * namePath.match(profilePath) // { relation: "child" } — namePath is deeper
158
197
  */
159
198
  match(other: ResolvablePath<T>): MatchResult | null;
160
199
  /**
161
- * Appends another path to the end of this path. If the end of this path matches
162
- * the beginning of the other path, the overlapping segments are intelligently deduplicated.
200
+ * Appends `other` (a T-rooted path) with smart suffix/prefix overlap deduplication.
201
+ * When the tail of this path matches the head of `other`, the overlap is collapsed once.
202
+ *
203
+ * If `other` carries wildcards (`*` / `**`), the result is a `TemplatePath` so the
204
+ * appended pattern still expands at `.get()` time; otherwise a concrete `Path`.
205
+ * Narrow at the call site if you need to disambiguate.
163
206
  *
164
207
  * @example
165
- * const base = path<Root>().users;
166
- * const full = base.merge(p => p[0].name); // equivalent to path<Root>().users[0].name
208
+ * const base = path<Root>(r => r.users[0].profile);
209
+ * base.merge(r => r.users[0].profile.name) // "users.0.profile.name" (no duplication)
167
210
  */
168
- merge<U>(other: ResolvablePath<T, U>): Path<T, U, string>;
211
+ merge<U>(other: ResolvablePath<T, U>): Path<T, U> | TemplatePath<T, U>;
169
212
  /**
170
- * Removes the segments of another path from either the beginning or the end of this path.
171
- * Returns `null` if the other path is neither a prefix nor a suffix.
213
+ * Removes `prefix` from the start of this path and returns the remaining tail.
214
+ * Returns `null` when `prefix` is not a leading segment-sequence of this path.
215
+ *
216
+ * The returned path carries the correct root type (`U` — the type `prefix` resolves to),
217
+ * so it can be passed directly to `.to()` or used independently.
172
218
  *
173
219
  * @example
174
- * const full = path<Root>().users[0].name;
175
- * const base = path<Root>().users;
176
- * const remainder = full.subtract(base); // equivalent to path()[0].name
220
+ * const full = path<Company>(c => c.departments[0].employees[0].name);
221
+ * const prefix = path<Company>(c => c.departments[0]);
222
+ * full.subtract(prefix) // Path<Department, string>
177
223
  */
178
- subtract(other: ResolvablePath<T>): Path<T, V, string> | null;
224
+ subtract<U>(prefix: ResolvablePath<T, U>): Path<U, V> | null;
179
225
  /**
180
- * Returns a new path containing a subset of the segments, similar to Array.prototype.slice.
226
+ * Returns a new path over a slice of segments, following `Array.prototype.slice` semantics.
227
+ * Value type becomes `unknown` because the type at an arbitrary segment boundary is not statically inferable.
181
228
  *
182
229
  * @example
183
- * const full = path<Root>().users[0].name;
184
- * full.slice(0, 1); // equivalent to path<Root>().users
230
+ * path<Root>(r => r.users[0].name).slice(0, 2).$ // "users.0"
185
231
  */
186
- slice(start?: number, end?: number): Path<T, unknown, string>;
232
+ slice(start?: number, end?: number): Path<T, unknown>;
187
233
  /**
188
- * Extends the current path using a lambda expression starting from the resolved value.
234
+ * Extends this path with a relative path rooted at `V`.
235
+ * Accepts a lambda expression, a pre-built `Path<V, U>`, a `TemplatePath<V, U>`, or any `{segments}` object.
236
+ *
237
+ * If `relative` carries wildcards (`*` / `**`), the result is a `TemplatePath` so the
238
+ * appended pattern still expands at `.get()` time; otherwise a concrete `Path`.
239
+ * Narrow at the call site if you need to disambiguate.
189
240
  *
190
241
  * @example
191
- * const userPath = path<Root>().users[0];
192
- * const namePath = userPath.to(u => u.name);
242
+ * // Lambda form:
243
+ * employeePath.to(e => e.profile.firstName)
244
+ *
245
+ * // Pre-built path form (no extra lambda needed):
246
+ * const firstName = path<Employee>(e => e.profile.firstName);
247
+ * employeePath.to(firstName)
193
248
  */
194
- to<U>(expr: PathExpression<V, U>): Path<T, U, string>;
249
+ to<U>(relative: ResolvablePath<V, U>): Path<T, U> | TemplatePath<T, U>;
195
250
  }
196
251
  /**
197
- * Represents a strongly-typed object property path.
252
+ * A strongly-typed object property path.
253
+ * `.each()` and `.deep()` are only present when `V` is not a primitive type.
198
254
  *
199
- * This type uses intersection (`&`) to combine the base operations (`BasePath`)
200
- * with conditional traversal methods (`TraversablePathMethods`). The conditional
201
- * check `[V] extends [Primitive]` ensures that IDEs will not suggest `.each()` or
202
- * `.deep()` when the path has resolved to a primitive value (like a string or number).
203
- *
204
- * @template T Root data type
205
- * @template V Resolved value type at path end
206
- * @template P Path string (e.g. "a.b.c") — literal when inferrable, string when dynamic
255
+ * @template T Root data type
256
+ * @template V Resolved value type at the end of the path
207
257
  */
208
- type Path<T = unknown, V = unknown, P extends string = string> = BasePath<T, V, P> & ([V] extends [Primitive] ? {} : TraversablePathMethods<T, V>);
258
+ type Path<T = unknown, V = unknown> = BasePath<T, V> & ([V] extends [Primitive] ? {} : TraversablePathMethods<T, V>);
209
259
  /**
210
- * Represents a path containing wildcards (`*` or `**`), useful for operations on multiple items.
211
- *
212
- * It extends the standard `Path` concept but alters the return types of `.each()` and `.deep()`
213
- * to return another `TemplatePath` (chaining templates). It also adds the `.expand()` method
214
- * which can resolve this template against actual data to return an array of concrete `Path`s.
260
+ * A path that contains wildcards (`*` or `**`), matching multiple values at once.
215
261
  *
216
- * **Data Access:** Calling `.get()` on a `TemplatePath` will return an array of all matched values.
217
- * Calling `.set()` will immutably update all matched paths in the object and return the updated object.
262
+ * - `.get(data)` returns an array of all matched values.
263
+ * - `.set(data, value)` immutably sets every match to the same constant.
264
+ * - `.update(data, fn)` applies a per-item transform to every match.
265
+ * - `.expand(data)` resolves the template to an array of concrete `Path` objects.
218
266
  *
219
- * @template T Root data type
220
- * @template V Resolved value type at path end
221
- * @template P Path string (e.g. "a.*.c")
267
+ * @template T Root data type
268
+ * @template V Item value type at the end of the template path
222
269
  */
223
- type TemplatePath<T = unknown, V = unknown, P extends string = string> = (Omit<BasePath<T, V, P>, "get" | "fn"> & {
270
+ type TemplatePath<T = unknown, V = unknown> = Omit<BasePath<T, V>, "get" | "fn" | "to" | "merge" | "subtract" | "parent" | "slice"> & {
224
271
  /**
225
- * Extracts an array of values at this template path from the given data object.
272
+ * Returns an array of all values matched by this template.
226
273
  *
227
274
  * @example
228
- * const names = path<Root>().users.each().name.get(data); // string[]
275
+ * path<Root>().users.each(u => u.name).get(data) // string[]
229
276
  */
230
277
  get(data: T): V[];
231
278
  /**
232
- * Returns an accessor function that extracts an array of values at this template path from the given data object.
233
- * Useful for array methods like `.map()` or `.filter()`.
279
+ * Pre-bound accessor function returning an array of all matched values.
234
280
  *
235
281
  * @example
236
- * const allNames = companies.map(path<Company>().departments.each().name.fn);
282
+ * companies.map(path<Company>().departments.each(d => d.name).fn)
237
283
  */
238
284
  readonly fn: (data: T) => V[];
239
- }) & ([V] extends [Primitive] ? {} : {
240
285
  /**
241
- * Traverses into a collection (Array or Record) to operate on each item, returning a TemplatePath.
286
+ * Resolves this template to an array of concrete paths that exist in `data`.
242
287
  *
243
288
  * @example
244
- * const users = path<Root>().users;
245
- * const userNames = users.each(u => u.name); // TemplatePath matching all names
289
+ * path<Root>().users.each().name.expand(data)
290
+ * // [path<Root>().users[0].name, path<Root>().users[1].name, ...]
246
291
  */
247
- each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U, `${string}.${"*"}.${string}`>;
292
+ expand(data: T): Path<T, V>[];
248
293
  /**
249
- * Traverses deeply into a structure, matching any nested property, returning a TemplatePath.
294
+ * Extends this template with a relative path rooted at `V`, preserving wildcard expansion.
295
+ * Returns a `TemplatePath` so the full chain (including the appended segments) is template-aware.
250
296
  *
251
297
  * @example
252
- * const root = path<Root>();
253
- * const allIds = root.deep(node => node.id); // TemplatePath matching any 'id' at any depth
298
+ * path<Root>(r => r.users).each().to(u => u.name)
299
+ * // TemplatePath collects every user's name
254
300
  */
255
- deep<U = DeepReachable<V>>(expr?: (leaf: DeepReachable<V>) => U): TemplatePath<T, U, `${string}.${"**"}.${string}`>;
256
- }) & {
301
+ to<U>(relative: ResolvablePath<V, U>): TemplatePath<T, U>;
257
302
  /**
258
- * Resolves this template path against actual data to return an array of concrete paths
259
- * that exist in the given data.
260
- *
261
- * Note: Currently, `expand` only supports evaluating a single wildcard (`*` or `**`) per path.
262
- *
263
- * @example
264
- * const template = path<Root>().users.each().name;
265
- * const concretePaths = template.expand(data); // [path<Root>().users[0].name, ...]
303
+ * Appends `other` with smart overlap deduplication, preserving wildcard behavior.
304
+ * Returns a `TemplatePath` because `this` carries wildcards.
266
305
  */
267
- expand(data: T): Path<T, V, string>[];
268
- };
306
+ merge<U>(other: ResolvablePath<T, U>): TemplatePath<T, U>;
307
+ /**
308
+ * Returns the parent path (all segments except the last), or `null` for an empty path.
309
+ * If the parent still contains wildcards (`*` / `**`) it remains a `TemplatePath`;
310
+ * otherwise a concrete `Path` is returned. Narrow at the call site if needed.
311
+ */
312
+ parent(): Path<T, unknown> | TemplatePath<T, unknown> | null;
313
+ /**
314
+ * Returns a new path over a slice of segments. If the slice still contains wildcards
315
+ * the result is a `TemplatePath`; otherwise a concrete `Path`.
316
+ */
317
+ slice(start?: number, end?: number): Path<T, unknown> | TemplatePath<T, unknown>;
318
+ /**
319
+ * Removes `prefix` from the start of this path and returns the remaining tail.
320
+ * If the tail still contains wildcards the result is a `TemplatePath<U, V>`;
321
+ * otherwise a concrete `Path<U, V>`. Returns `null` when `prefix` is not a leading segment-sequence.
322
+ */
323
+ subtract<U>(prefix: ResolvablePath<T, U>): Path<U, V> | TemplatePath<U, V> | null;
324
+ } & ([V] extends [Primitive] ? {} : {
325
+ each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U>;
326
+ deep<U = V>(expr?: (leaf: V) => U): TemplatePath<T, U>;
327
+ });
269
328
  /**
270
- * Constructor overloads for creating paths.
271
- *
272
- * This allows the `path()` function to be called in several ways:
273
- * 1. Without arguments: returns a root path `path<T>()`.
274
- * 2. With a lambda: returns a path built from the expression `path<T>((p) => p.a.b)`.
275
- * 3. With a base path and a lambda: allows extending an existing path `path(base, (p) => p.c)`.
329
+ * The overloaded call signature of the `path()` function.
276
330
  */
277
331
  type PathConstructor = {
278
- <T>(): Path<T, T, "">;
279
- <T, V = unknown>(expr: PathExpression<T, V>): Path<T, V, string>;
280
- <T, U, V = unknown>(base: BasePath<T, U, string>, expr: PathExpression<U, V>): Path<T, V, string>;
332
+ <T>(): Path<T, T>;
333
+ <T, V = unknown>(expr: PathExpression<T, V>): Path<T, V>;
334
+ <T, U, V = unknown>(base: BasePath<T, U>, expr: PathExpression<U, V>): Path<T, V>;
281
335
  };
282
- /** Unsafe path from string no type checking on segments */
283
- type UnsafePathConstructor = <T>(raw: string) => Path<T, unknown, string>;
336
+ /** Call signature of the `unsafePath()` function */
337
+ type UnsafePathConstructor = <T, V = unknown>(raw: string) => Path<T, V>;
284
338
 
285
339
  /**
286
- * Create a typed path from a lambda expression or extend an existing base path.
340
+ * Create a typed path from a lambda expression, from an existing base path, or as a root.
287
341
  *
288
- * This function serves as the primary entry point for constructing paths. By utilizing
289
- * a Proxy-based builder (the lambda expression), it captures property accesses and
290
- * records them as path segments without needing to evaluate actual data.
342
+ * @example
343
+ * // Lambda recommended: annotate the parameter so both T and V are inferred
344
+ * const p = path((user: User) => user.profile.firstName);
291
345
  *
292
346
  * @example
293
- * // Create a root path
294
- * const root = path<User>();
347
+ * // Both generics explicit
348
+ * const p = path<User, string>((u) => u.profile.firstName);
295
349
  *
296
350
  * @example
297
- * // Create a path via lambda
298
- * const p = path<User>((u) => u.address.city);
351
+ * // Root path (no segments) — typically extended via .to() or path(root, expr)
352
+ * const root = path<User>();
299
353
  *
300
354
  * @example
301
- * // Extend an existing path
355
+ * // Extend an existing base path
302
356
  * const p2 = path(root, (u) => u.profile);
303
357
  */
304
- declare function path<T>(): Path<T, T, "">;
305
- declare function path<T, V = unknown>(expr: PathExpression<T, V>): Path<T, V, string>;
306
- declare function path<T, U, V = unknown>(base: BasePath<T, U, string>, expr: PathExpression<U, V>): Path<T, V, string>;
358
+ declare function path<T>(): Path<T, T>;
359
+ declare function path<T, V = unknown>(expr: PathExpression<T, V>): Path<T, V>;
360
+ declare function path<T, U, V = unknown>(base: BasePath<T, U>, expr: PathExpression<U, V>): Path<T, V>;
307
361
  /**
308
- * Create a path from a raw string (e.g., "users.0.name").
362
+ * Create a path from a raw dot-separated string (e.g. `"users.0.name"`).
363
+ *
364
+ * Useful for dynamic paths from external sources (API responses, Zod issue paths, etc.).
365
+ * Segments that are canonical non-negative integers are stored as numbers; all others as strings.
309
366
  *
310
- * This is useful when paths are dynamic (like from a database or API response).
311
- * Type checking on individual segments is bypassed, and segments are automatically
312
- * parsed into numeric indices where appropriate.
367
+ * The optional second generic `V` declares the expected leaf type without a cast:
368
+ * ```ts
369
+ * unsafePath<User, string>("profile.firstName").get(user) // string | undefined
370
+ * ```
313
371
  *
314
- * @param raw The dot-separated path string.
315
- * @returns A Path object representing the given string.
372
+ * @param raw Dot-separated string. Empty string returns a zero-segment root path.
316
373
  */
317
- declare function unsafePath<T>(raw: string): Path<T, unknown, string>;
374
+ declare function unsafePath<T, V = unknown>(raw: string): Path<T, V>;
318
375
 
319
- export { type DeepReachable, type MatchRelation, type MatchResult, type Path, type PathConstructor, type PathExpression, type ResolvedType, type Segment, type TemplatePath, type UnsafePathConstructor, path, unsafePath };
376
+ export { type BasePath, type CollectionItem, DEEP_WILDCARD, type MatchRelation, type MatchResult, type Path, type PathConstructor, type PathExpression, type Primitive, type ResolvablePath, type ResolvedType, type Segment, type TemplatePath, type TraversablePathMethods, type UnsafePathConstructor, WILDCARD, path, unsafePath };