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.
@@ -0,0 +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
+
21
+ /**
22
+ * Core type definitions for data-path.
23
+ */
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 */
53
+ interface MatchResult {
54
+ relation: MatchRelation;
55
+ }
56
+ /**
57
+ * Extracts the resolved value type from a path object.
58
+ *
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.
69
+ */
70
+ type CollectionItem<V> = V extends ReadonlyArray<infer U> ? U : V extends Record<PropertyKey, infer U> ? U : unknown;
71
+ /** Primitive types that cannot be traversed — `.each()` and `.deep()` are hidden when V extends Primitive */
72
+ type Primitive = string | number | boolean | symbol | bigint | null | undefined;
73
+ /** Lambda used to build a path — receives a proxy typed as T, infers the path from property access */
74
+ type PathExpression<T, R = unknown> = (proxy: T) => R;
75
+ /**
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.
78
+ */
79
+ type ResolvablePath<T, V = unknown> = BasePath<T, V> | {
80
+ segments: readonly Segment[];
81
+ } | PathExpression<T, V>;
82
+ /**
83
+ * Traversal methods — only present when V is not a primitive type.
84
+ * Conditionally excluded by the Path and TemplatePath types.
85
+ */
86
+ interface TraversablePathMethods<T, V> {
87
+ /**
88
+ * Traverses into a collection (Array or Record), inserting a `*` wildcard.
89
+ * Returns a TemplatePath that matches every item.
90
+ *
91
+ * @example
92
+ * path<Root>().users.each(u => u.name) // TemplatePath — all names
93
+ * path<Root>().users.each() // TemplatePath — all items
94
+ */
95
+ each<U = CollectionItem<V>>(expr?: (item: CollectionItem<V>) => U): TemplatePath<T, U>;
96
+ /**
97
+ * Traverses deeply into a structure, inserting a `**` wildcard.
98
+ * Returns a TemplatePath that matches the given property at any nesting depth.
99
+ *
100
+ * @example
101
+ * path<Root>().tree.deep(node => node.id) // TemplatePath — any nested 'id'
102
+ * path<Root>().tree.deep() // TemplatePath — every descendant node
103
+ */
104
+ deep<U = V>(expr?: (leaf: V) => U): TemplatePath<T, U>;
105
+ }
106
+ /**
107
+ * The foundational interface shared by both Path and TemplatePath.
108
+ *
109
+ * @template T Root data type the path operates on
110
+ * @template V Resolved value type at the end of the path
111
+ */
112
+ interface BasePath<T = unknown, V = unknown> {
113
+ /** Ordered array of string keys and numeric indices that compose this path. */
114
+ readonly segments: readonly Segment[];
115
+ /** Number of segments in this path. */
116
+ readonly length: number;
117
+ /**
118
+ * Dot-notation string representation (e.g. `"users.0.name"`).
119
+ * Convenient for binding paths to form libraries.
120
+ *
121
+ * @example
122
+ * path<Root>(r => r.users[0].name).$ // "users.0.name"
123
+ */
124
+ readonly $: string;
125
+ /** Returns the dot-notation string representation. */
126
+ toString(): string;
127
+ /**
128
+ * Extracts the value at this path from a data object.
129
+ * Returns `undefined` — rather than throwing — when any intermediate segment is missing.
130
+ *
131
+ * @example
132
+ * path<User>(u => u.profile.name).get(user) // string | undefined
133
+ */
134
+ get(data: T): V | undefined;
135
+ /**
136
+ * Pre-bound accessor function. Useful for array higher-order methods.
137
+ *
138
+ * @example
139
+ * users.map(path<User>(u => u.name).fn) // string | undefined[]
140
+ */
141
+ readonly fn: (data: T) => V | undefined;
142
+ /**
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.
146
+ *
147
+ * @example
148
+ * path<User>(u => u.name).set(user, "Alice")
149
+ */
150
+ set(data: T, value: V): T;
151
+ /**
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).
155
+ *
156
+ * @example
157
+ * namePath.update(user, name => (name ?? "").toUpperCase())
158
+ */
159
+ update(data: T, updater: (current: V | undefined) => V): T;
160
+ /**
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.
163
+ *
164
+ * @example
165
+ * path<User>(u => u.profile.name).parent()?.$ // "profile"
166
+ * path<User>().parent() // null
167
+ */
168
+ parent(): Path<T, unknown> | null;
169
+ /**
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".
181
+ *
182
+ * @example
183
+ * profilePath.covers(namePath) // true — "profile" covers "profile.name"
184
+ * namePath.covers(profilePath) // false
185
+ */
186
+ covers(other: ResolvablePath<T>): boolean;
187
+ /** Returns `true` if this path is segment-by-segment identical to `other`. */
188
+ equals(other: ResolvablePath<T>): boolean;
189
+ /**
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.
193
+ *
194
+ * @example
195
+ * profilePath.match(namePath) // { relation: "parent" } — profilePath IS the parent
196
+ * namePath.match(profilePath) // { relation: "child" } — namePath is deeper
197
+ */
198
+ match(other: ResolvablePath<T>): MatchResult | null;
199
+ /**
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.
206
+ *
207
+ * @example
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)
210
+ */
211
+ merge<U>(other: ResolvablePath<T, U>): Path<T, U> | TemplatePath<T, U>;
212
+ /**
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.
218
+ *
219
+ * @example
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>
223
+ */
224
+ subtract<U>(prefix: ResolvablePath<T, U>): Path<U, V> | null;
225
+ /**
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.
228
+ *
229
+ * @example
230
+ * path<Root>(r => r.users[0].name).slice(0, 2).$ // "users.0"
231
+ */
232
+ slice(start?: number, end?: number): Path<T, unknown>;
233
+ /**
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.
240
+ *
241
+ * @example
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)
248
+ */
249
+ to<U>(relative: ResolvablePath<V, U>): Path<T, U> | TemplatePath<T, U>;
250
+ }
251
+ /**
252
+ * A strongly-typed object property path.
253
+ * `.each()` and `.deep()` are only present when `V` is not a primitive type.
254
+ *
255
+ * @template T Root data type
256
+ * @template V Resolved value type at the end of the path
257
+ */
258
+ type Path<T = unknown, V = unknown> = BasePath<T, V> & ([V] extends [Primitive] ? {} : TraversablePathMethods<T, V>);
259
+ /**
260
+ * A path that contains wildcards (`*` or `**`), matching multiple values at once.
261
+ *
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.
266
+ *
267
+ * @template T Root data type
268
+ * @template V Item value type at the end of the template path
269
+ */
270
+ type TemplatePath<T = unknown, V = unknown> = Omit<BasePath<T, V>, "get" | "fn" | "to" | "merge" | "subtract" | "parent" | "slice"> & {
271
+ /**
272
+ * Returns an array of all values matched by this template.
273
+ *
274
+ * @example
275
+ * path<Root>().users.each(u => u.name).get(data) // string[]
276
+ */
277
+ get(data: T): V[];
278
+ /**
279
+ * Pre-bound accessor function returning an array of all matched values.
280
+ *
281
+ * @example
282
+ * companies.map(path<Company>().departments.each(d => d.name).fn)
283
+ */
284
+ readonly fn: (data: T) => V[];
285
+ /**
286
+ * Resolves this template to an array of concrete paths that exist in `data`.
287
+ *
288
+ * @example
289
+ * path<Root>().users.each().name.expand(data)
290
+ * // [path<Root>().users[0].name, path<Root>().users[1].name, ...]
291
+ */
292
+ expand(data: T): Path<T, V>[];
293
+ /**
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.
296
+ *
297
+ * @example
298
+ * path<Root>(r => r.users).each().to(u => u.name)
299
+ * // TemplatePath — collects every user's name
300
+ */
301
+ to<U>(relative: ResolvablePath<V, U>): TemplatePath<T, U>;
302
+ /**
303
+ * Appends `other` with smart overlap deduplication, preserving wildcard behavior.
304
+ * Returns a `TemplatePath` because `this` carries wildcards.
305
+ */
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
+ });
328
+ /**
329
+ * The overloaded call signature of the `path()` function.
330
+ */
331
+ type PathConstructor = {
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>;
335
+ };
336
+ /** Call signature of the `unsafePath()` function */
337
+ type UnsafePathConstructor = <T, V = unknown>(raw: string) => Path<T, V>;
338
+
339
+ /**
340
+ * Create a typed path from a lambda expression, from an existing base path, or as a root.
341
+ *
342
+ * @example
343
+ * // Lambda — recommended: annotate the parameter so both T and V are inferred
344
+ * const p = path((user: User) => user.profile.firstName);
345
+ *
346
+ * @example
347
+ * // Both generics explicit
348
+ * const p = path<User, string>((u) => u.profile.firstName);
349
+ *
350
+ * @example
351
+ * // Root path (no segments) — typically extended via .to() or path(root, expr)
352
+ * const root = path<User>();
353
+ *
354
+ * @example
355
+ * // Extend an existing base path
356
+ * const p2 = path(root, (u) => u.profile);
357
+ */
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>;
361
+ /**
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.
366
+ *
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
+ * ```
371
+ *
372
+ * @param raw Dot-separated string. Empty string returns a zero-segment root path.
373
+ */
374
+ declare function unsafePath<T, V = unknown>(raw: string): Path<T, V>;
375
+
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 };