data-path 1.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/LICENSE +22 -0
- package/README.md +377 -0
- package/dist/index.d.mts +319 -0
- package/dist/index.d.ts +319 -0
- package/dist/index.js +527 -0
- package/dist/index.mjs +499 -0
- package/package.json +75 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for data-path.
|
|
3
|
+
* @see spec/idea.md
|
|
4
|
+
*/
|
|
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 */
|
|
10
|
+
interface MatchResult {
|
|
11
|
+
relation: MatchRelation;
|
|
12
|
+
params?: Record<string, string>;
|
|
13
|
+
}
|
|
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
|
+
/**
|
|
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.
|
|
21
|
+
*
|
|
22
|
+
* @template V The collection type (e.g., `string[]` or `Record<string, number>`)
|
|
23
|
+
*/
|
|
24
|
+
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 */
|
|
26
|
+
type Primitive = string | number | boolean | symbol | bigint | null | undefined;
|
|
27
|
+
/** Expression type for path construction — receives proxy, returns any (path is inferred from access) */
|
|
28
|
+
type PathExpression<T, R = unknown> = (proxy: T) => R;
|
|
29
|
+
/**
|
|
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.
|
|
36
|
+
*/
|
|
37
|
+
type ResolvablePath<T, V = unknown> = BasePath<T, V, string> | {
|
|
38
|
+
segments: readonly Segment[];
|
|
39
|
+
} | PathExpression<T, V>;
|
|
40
|
+
/**
|
|
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).
|
|
45
|
+
*/
|
|
46
|
+
interface TraversablePathMethods<T, V> {
|
|
47
|
+
/**
|
|
48
|
+
* Traverses into a collection (Array or Record) to operate on each item.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const users = path<Root>().users;
|
|
52
|
+
* const userNames = users.each(u => u.name); // Path matches all names
|
|
53
|
+
*/
|
|
54
|
+
each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Traverses deeply into a structure, matching any nested property.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const root = path<Root>();
|
|
60
|
+
* const allIds = root.deep(node => node.id); // Path matches any 'id' at any depth
|
|
61
|
+
*/
|
|
62
|
+
deep<U = DeepReachable<V>>(expr?: (leaf: DeepReachable<V>) => U): TemplatePath<T, U, string>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
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.
|
|
68
|
+
*
|
|
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).
|
|
72
|
+
*/
|
|
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
|
+
*/
|
|
77
|
+
readonly segments: readonly Segment[];
|
|
78
|
+
/**
|
|
79
|
+
* The number of segments in this path.
|
|
80
|
+
*/
|
|
81
|
+
readonly length: number;
|
|
82
|
+
/**
|
|
83
|
+
* The string representation of the path (e.g. "users.0.name").
|
|
84
|
+
* Useful for binding paths to form libraries or UI components.
|
|
85
|
+
*
|
|
86
|
+
* @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"
|
|
95
|
+
*/
|
|
96
|
+
toString(): string;
|
|
97
|
+
/**
|
|
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.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const namePath = path<User>().name;
|
|
103
|
+
* const name = namePath.get({ name: "Alice" }); // "Alice"
|
|
104
|
+
*/
|
|
105
|
+
get(data: T): V;
|
|
106
|
+
/**
|
|
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()`.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* const names = users.map(path<User>().name.fn);
|
|
112
|
+
*/
|
|
113
|
+
readonly fn: (data: T) => V;
|
|
114
|
+
/**
|
|
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).
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const namePath = path<User>().name;
|
|
121
|
+
* const updatedUser = namePath.set({ name: "Alice" }, "Bob"); // { name: "Bob" }
|
|
122
|
+
*/
|
|
123
|
+
set(data: T, value: V): T;
|
|
124
|
+
/**
|
|
125
|
+
* Checks if this path starts with the segments of another path.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const a = path<Root>().users[0].name;
|
|
129
|
+
* const b = path<Root>().users;
|
|
130
|
+
* a.startsWith(b); // true
|
|
131
|
+
*/
|
|
132
|
+
startsWith(other: ResolvablePath<T>): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Checks if this path encompasses the segments of another path (i.e., this path is a prefix of the other).
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* const a = path<Root>().users;
|
|
138
|
+
* const b = path<Root>().users[0].name;
|
|
139
|
+
* a.includes(b); // true
|
|
140
|
+
*/
|
|
141
|
+
includes(other: ResolvablePath<T>): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Checks if this path is exactly equal to another path.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* const a = path<Root>().users;
|
|
147
|
+
* const b = path<Root>().users;
|
|
148
|
+
* a.equals(b); // true
|
|
149
|
+
*/
|
|
150
|
+
equals(other: ResolvablePath<T>): boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Matches this path against another path, returning their relationship.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const a = path<Root>().users[0];
|
|
156
|
+
* const b = path<Root>().users;
|
|
157
|
+
* a.match(b); // { relation: 'child', params: {} }
|
|
158
|
+
*/
|
|
159
|
+
match(other: ResolvablePath<T>): MatchResult | null;
|
|
160
|
+
/**
|
|
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.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* const base = path<Root>().users;
|
|
166
|
+
* const full = base.merge(p => p[0].name); // equivalent to path<Root>().users[0].name
|
|
167
|
+
*/
|
|
168
|
+
merge<U>(other: ResolvablePath<T, U>): Path<T, U, string>;
|
|
169
|
+
/**
|
|
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.
|
|
172
|
+
*
|
|
173
|
+
* @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
|
|
177
|
+
*/
|
|
178
|
+
subtract(other: ResolvablePath<T>): Path<T, V, string> | null;
|
|
179
|
+
/**
|
|
180
|
+
* Returns a new path containing a subset of the segments, similar to Array.prototype.slice.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* const full = path<Root>().users[0].name;
|
|
184
|
+
* full.slice(0, 1); // equivalent to path<Root>().users
|
|
185
|
+
*/
|
|
186
|
+
slice(start?: number, end?: number): Path<T, unknown, string>;
|
|
187
|
+
/**
|
|
188
|
+
* Extends the current path using a lambda expression starting from the resolved value.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* const userPath = path<Root>().users[0];
|
|
192
|
+
* const namePath = userPath.to(u => u.name);
|
|
193
|
+
*/
|
|
194
|
+
to<U>(expr: PathExpression<V, U>): Path<T, U, string>;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Represents a strongly-typed object property path.
|
|
198
|
+
*
|
|
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
|
|
207
|
+
*/
|
|
208
|
+
type Path<T = unknown, V = unknown, P extends string = string> = BasePath<T, V, P> & ([V] extends [Primitive] ? {} : TraversablePathMethods<T, V>);
|
|
209
|
+
/**
|
|
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.
|
|
215
|
+
*
|
|
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.
|
|
218
|
+
*
|
|
219
|
+
* @template T Root data type
|
|
220
|
+
* @template V Resolved value type at path end
|
|
221
|
+
* @template P Path string (e.g. "a.*.c")
|
|
222
|
+
*/
|
|
223
|
+
type TemplatePath<T = unknown, V = unknown, P extends string = string> = (Omit<BasePath<T, V, P>, "get" | "fn"> & {
|
|
224
|
+
/**
|
|
225
|
+
* Extracts an array of values at this template path from the given data object.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* const names = path<Root>().users.each().name.get(data); // string[]
|
|
229
|
+
*/
|
|
230
|
+
get(data: T): V[];
|
|
231
|
+
/**
|
|
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()`.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const allNames = companies.map(path<Company>().departments.each().name.fn);
|
|
237
|
+
*/
|
|
238
|
+
readonly fn: (data: T) => V[];
|
|
239
|
+
}) & ([V] extends [Primitive] ? {} : {
|
|
240
|
+
/**
|
|
241
|
+
* Traverses into a collection (Array or Record) to operate on each item, returning a TemplatePath.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* const users = path<Root>().users;
|
|
245
|
+
* const userNames = users.each(u => u.name); // TemplatePath matching all names
|
|
246
|
+
*/
|
|
247
|
+
each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U, `${string}.${"*"}.${string}`>;
|
|
248
|
+
/**
|
|
249
|
+
* Traverses deeply into a structure, matching any nested property, returning a TemplatePath.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* const root = path<Root>();
|
|
253
|
+
* const allIds = root.deep(node => node.id); // TemplatePath matching any 'id' at any depth
|
|
254
|
+
*/
|
|
255
|
+
deep<U = DeepReachable<V>>(expr?: (leaf: DeepReachable<V>) => U): TemplatePath<T, U, `${string}.${"**"}.${string}`>;
|
|
256
|
+
}) & {
|
|
257
|
+
/**
|
|
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, ...]
|
|
266
|
+
*/
|
|
267
|
+
expand(data: T): Path<T, V, string>[];
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
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)`.
|
|
276
|
+
*/
|
|
277
|
+
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>;
|
|
281
|
+
};
|
|
282
|
+
/** Unsafe path from string — no type checking on segments */
|
|
283
|
+
type UnsafePathConstructor = <T>(raw: string) => Path<T, unknown, string>;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Create a typed path from a lambda expression or extend an existing base path.
|
|
287
|
+
*
|
|
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.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* // Create a root path
|
|
294
|
+
* const root = path<User>();
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* // Create a path via lambda
|
|
298
|
+
* const p = path<User>((u) => u.address.city);
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* // Extend an existing path
|
|
302
|
+
* const p2 = path(root, (u) => u.profile);
|
|
303
|
+
*/
|
|
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>;
|
|
307
|
+
/**
|
|
308
|
+
* Create a path from a raw string (e.g., "users.0.name").
|
|
309
|
+
*
|
|
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.
|
|
313
|
+
*
|
|
314
|
+
* @param raw The dot-separated path string.
|
|
315
|
+
* @returns A Path object representing the given string.
|
|
316
|
+
*/
|
|
317
|
+
declare function unsafePath<T>(raw: string): Path<T, unknown, string>;
|
|
318
|
+
|
|
319
|
+
export { type DeepReachable, type MatchRelation, type MatchResult, type Path, type PathConstructor, type PathExpression, type ResolvedType, type Segment, type TemplatePath, type UnsafePathConstructor, path, unsafePath };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for data-path.
|
|
3
|
+
* @see spec/idea.md
|
|
4
|
+
*/
|
|
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 */
|
|
10
|
+
interface MatchResult {
|
|
11
|
+
relation: MatchRelation;
|
|
12
|
+
params?: Record<string, string>;
|
|
13
|
+
}
|
|
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
|
+
/**
|
|
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.
|
|
21
|
+
*
|
|
22
|
+
* @template V The collection type (e.g., `string[]` or `Record<string, number>`)
|
|
23
|
+
*/
|
|
24
|
+
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 */
|
|
26
|
+
type Primitive = string | number | boolean | symbol | bigint | null | undefined;
|
|
27
|
+
/** Expression type for path construction — receives proxy, returns any (path is inferred from access) */
|
|
28
|
+
type PathExpression<T, R = unknown> = (proxy: T) => R;
|
|
29
|
+
/**
|
|
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.
|
|
36
|
+
*/
|
|
37
|
+
type ResolvablePath<T, V = unknown> = BasePath<T, V, string> | {
|
|
38
|
+
segments: readonly Segment[];
|
|
39
|
+
} | PathExpression<T, V>;
|
|
40
|
+
/**
|
|
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).
|
|
45
|
+
*/
|
|
46
|
+
interface TraversablePathMethods<T, V> {
|
|
47
|
+
/**
|
|
48
|
+
* Traverses into a collection (Array or Record) to operate on each item.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const users = path<Root>().users;
|
|
52
|
+
* const userNames = users.each(u => u.name); // Path matches all names
|
|
53
|
+
*/
|
|
54
|
+
each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Traverses deeply into a structure, matching any nested property.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const root = path<Root>();
|
|
60
|
+
* const allIds = root.deep(node => node.id); // Path matches any 'id' at any depth
|
|
61
|
+
*/
|
|
62
|
+
deep<U = DeepReachable<V>>(expr?: (leaf: DeepReachable<V>) => U): TemplatePath<T, U, string>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
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.
|
|
68
|
+
*
|
|
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).
|
|
72
|
+
*/
|
|
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
|
+
*/
|
|
77
|
+
readonly segments: readonly Segment[];
|
|
78
|
+
/**
|
|
79
|
+
* The number of segments in this path.
|
|
80
|
+
*/
|
|
81
|
+
readonly length: number;
|
|
82
|
+
/**
|
|
83
|
+
* The string representation of the path (e.g. "users.0.name").
|
|
84
|
+
* Useful for binding paths to form libraries or UI components.
|
|
85
|
+
*
|
|
86
|
+
* @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"
|
|
95
|
+
*/
|
|
96
|
+
toString(): string;
|
|
97
|
+
/**
|
|
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.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const namePath = path<User>().name;
|
|
103
|
+
* const name = namePath.get({ name: "Alice" }); // "Alice"
|
|
104
|
+
*/
|
|
105
|
+
get(data: T): V;
|
|
106
|
+
/**
|
|
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()`.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* const names = users.map(path<User>().name.fn);
|
|
112
|
+
*/
|
|
113
|
+
readonly fn: (data: T) => V;
|
|
114
|
+
/**
|
|
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).
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const namePath = path<User>().name;
|
|
121
|
+
* const updatedUser = namePath.set({ name: "Alice" }, "Bob"); // { name: "Bob" }
|
|
122
|
+
*/
|
|
123
|
+
set(data: T, value: V): T;
|
|
124
|
+
/**
|
|
125
|
+
* Checks if this path starts with the segments of another path.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const a = path<Root>().users[0].name;
|
|
129
|
+
* const b = path<Root>().users;
|
|
130
|
+
* a.startsWith(b); // true
|
|
131
|
+
*/
|
|
132
|
+
startsWith(other: ResolvablePath<T>): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Checks if this path encompasses the segments of another path (i.e., this path is a prefix of the other).
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* const a = path<Root>().users;
|
|
138
|
+
* const b = path<Root>().users[0].name;
|
|
139
|
+
* a.includes(b); // true
|
|
140
|
+
*/
|
|
141
|
+
includes(other: ResolvablePath<T>): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Checks if this path is exactly equal to another path.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* const a = path<Root>().users;
|
|
147
|
+
* const b = path<Root>().users;
|
|
148
|
+
* a.equals(b); // true
|
|
149
|
+
*/
|
|
150
|
+
equals(other: ResolvablePath<T>): boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Matches this path against another path, returning their relationship.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* const a = path<Root>().users[0];
|
|
156
|
+
* const b = path<Root>().users;
|
|
157
|
+
* a.match(b); // { relation: 'child', params: {} }
|
|
158
|
+
*/
|
|
159
|
+
match(other: ResolvablePath<T>): MatchResult | null;
|
|
160
|
+
/**
|
|
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.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* const base = path<Root>().users;
|
|
166
|
+
* const full = base.merge(p => p[0].name); // equivalent to path<Root>().users[0].name
|
|
167
|
+
*/
|
|
168
|
+
merge<U>(other: ResolvablePath<T, U>): Path<T, U, string>;
|
|
169
|
+
/**
|
|
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.
|
|
172
|
+
*
|
|
173
|
+
* @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
|
|
177
|
+
*/
|
|
178
|
+
subtract(other: ResolvablePath<T>): Path<T, V, string> | null;
|
|
179
|
+
/**
|
|
180
|
+
* Returns a new path containing a subset of the segments, similar to Array.prototype.slice.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* const full = path<Root>().users[0].name;
|
|
184
|
+
* full.slice(0, 1); // equivalent to path<Root>().users
|
|
185
|
+
*/
|
|
186
|
+
slice(start?: number, end?: number): Path<T, unknown, string>;
|
|
187
|
+
/**
|
|
188
|
+
* Extends the current path using a lambda expression starting from the resolved value.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* const userPath = path<Root>().users[0];
|
|
192
|
+
* const namePath = userPath.to(u => u.name);
|
|
193
|
+
*/
|
|
194
|
+
to<U>(expr: PathExpression<V, U>): Path<T, U, string>;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Represents a strongly-typed object property path.
|
|
198
|
+
*
|
|
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
|
|
207
|
+
*/
|
|
208
|
+
type Path<T = unknown, V = unknown, P extends string = string> = BasePath<T, V, P> & ([V] extends [Primitive] ? {} : TraversablePathMethods<T, V>);
|
|
209
|
+
/**
|
|
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.
|
|
215
|
+
*
|
|
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.
|
|
218
|
+
*
|
|
219
|
+
* @template T Root data type
|
|
220
|
+
* @template V Resolved value type at path end
|
|
221
|
+
* @template P Path string (e.g. "a.*.c")
|
|
222
|
+
*/
|
|
223
|
+
type TemplatePath<T = unknown, V = unknown, P extends string = string> = (Omit<BasePath<T, V, P>, "get" | "fn"> & {
|
|
224
|
+
/**
|
|
225
|
+
* Extracts an array of values at this template path from the given data object.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* const names = path<Root>().users.each().name.get(data); // string[]
|
|
229
|
+
*/
|
|
230
|
+
get(data: T): V[];
|
|
231
|
+
/**
|
|
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()`.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const allNames = companies.map(path<Company>().departments.each().name.fn);
|
|
237
|
+
*/
|
|
238
|
+
readonly fn: (data: T) => V[];
|
|
239
|
+
}) & ([V] extends [Primitive] ? {} : {
|
|
240
|
+
/**
|
|
241
|
+
* Traverses into a collection (Array or Record) to operate on each item, returning a TemplatePath.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* const users = path<Root>().users;
|
|
245
|
+
* const userNames = users.each(u => u.name); // TemplatePath matching all names
|
|
246
|
+
*/
|
|
247
|
+
each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U, `${string}.${"*"}.${string}`>;
|
|
248
|
+
/**
|
|
249
|
+
* Traverses deeply into a structure, matching any nested property, returning a TemplatePath.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* const root = path<Root>();
|
|
253
|
+
* const allIds = root.deep(node => node.id); // TemplatePath matching any 'id' at any depth
|
|
254
|
+
*/
|
|
255
|
+
deep<U = DeepReachable<V>>(expr?: (leaf: DeepReachable<V>) => U): TemplatePath<T, U, `${string}.${"**"}.${string}`>;
|
|
256
|
+
}) & {
|
|
257
|
+
/**
|
|
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, ...]
|
|
266
|
+
*/
|
|
267
|
+
expand(data: T): Path<T, V, string>[];
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
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)`.
|
|
276
|
+
*/
|
|
277
|
+
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>;
|
|
281
|
+
};
|
|
282
|
+
/** Unsafe path from string — no type checking on segments */
|
|
283
|
+
type UnsafePathConstructor = <T>(raw: string) => Path<T, unknown, string>;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Create a typed path from a lambda expression or extend an existing base path.
|
|
287
|
+
*
|
|
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.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* // Create a root path
|
|
294
|
+
* const root = path<User>();
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* // Create a path via lambda
|
|
298
|
+
* const p = path<User>((u) => u.address.city);
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* // Extend an existing path
|
|
302
|
+
* const p2 = path(root, (u) => u.profile);
|
|
303
|
+
*/
|
|
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>;
|
|
307
|
+
/**
|
|
308
|
+
* Create a path from a raw string (e.g., "users.0.name").
|
|
309
|
+
*
|
|
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.
|
|
313
|
+
*
|
|
314
|
+
* @param raw The dot-separated path string.
|
|
315
|
+
* @returns A Path object representing the given string.
|
|
316
|
+
*/
|
|
317
|
+
declare function unsafePath<T>(raw: string): Path<T, unknown, string>;
|
|
318
|
+
|
|
319
|
+
export { type DeepReachable, type MatchRelation, type MatchResult, type Path, type PathConstructor, type PathExpression, type ResolvedType, type Segment, type TemplatePath, type UnsafePathConstructor, path, unsafePath };
|