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.mjs
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var PATH_SEGMENTS = /* @__PURE__ */ Symbol("PATH_SEGMENTS");
|
|
3
|
+
var WILDCARD = "*";
|
|
4
|
+
var DEEP_WILDCARD = "**";
|
|
5
|
+
|
|
6
|
+
// src/utils.ts
|
|
7
|
+
function isCanonicalArrayIndex(key) {
|
|
8
|
+
const num = Number(key);
|
|
9
|
+
return Number.isInteger(num) && num >= 0 && num < 2 ** 32 && String(num) === key;
|
|
10
|
+
}
|
|
11
|
+
function resolveSegments(target) {
|
|
12
|
+
if (typeof target === "function") {
|
|
13
|
+
const proxy = createPathProxy([]);
|
|
14
|
+
const result = target(proxy);
|
|
15
|
+
return result?.[PATH_SEGMENTS] ?? [];
|
|
16
|
+
}
|
|
17
|
+
if (target && typeof target === "object" && "segments" in target) {
|
|
18
|
+
return target.segments;
|
|
19
|
+
}
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
function createPathProxy(segments) {
|
|
23
|
+
return new Proxy(
|
|
24
|
+
{ [PATH_SEGMENTS]: segments },
|
|
25
|
+
{
|
|
26
|
+
get(target, key) {
|
|
27
|
+
if (key === PATH_SEGMENTS)
|
|
28
|
+
return target[PATH_SEGMENTS];
|
|
29
|
+
if (typeof key === "string" && key !== "Symbol") {
|
|
30
|
+
const next = isCanonicalArrayIndex(key) ? Number(key) : key;
|
|
31
|
+
return createPathProxy([...segments, next]);
|
|
32
|
+
}
|
|
33
|
+
return typeof key === "symbol" ? void 0 : createPathProxy(segments);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function segmentsEqual(a, b) {
|
|
39
|
+
if (a.length !== b.length) return false;
|
|
40
|
+
return a.every((s, i) => s === b[i]);
|
|
41
|
+
}
|
|
42
|
+
function matchesPrefix(full, prefix) {
|
|
43
|
+
if (prefix.length > full.length) return false;
|
|
44
|
+
let p = 0;
|
|
45
|
+
let f = 0;
|
|
46
|
+
while (p < prefix.length && f < full.length) {
|
|
47
|
+
if (prefix[p] === DEEP_WILDCARD) {
|
|
48
|
+
if (p === prefix.length - 1) return true;
|
|
49
|
+
const restPrefix = prefix.slice(p + 1);
|
|
50
|
+
for (let skip = 0; f + skip <= full.length; skip++) {
|
|
51
|
+
if (matchesPrefix(full.slice(f + skip), restPrefix)) return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (prefix[p] !== WILDCARD && prefix[p] !== full[f]) return false;
|
|
56
|
+
p++;
|
|
57
|
+
f++;
|
|
58
|
+
}
|
|
59
|
+
return p === prefix.length;
|
|
60
|
+
}
|
|
61
|
+
function patternMatches(pattern, concrete) {
|
|
62
|
+
if (pattern.length !== concrete.length) return false;
|
|
63
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
64
|
+
if (pattern[i] !== WILDCARD && pattern[i] !== DEEP_WILDCARD && pattern[i] !== concrete[i])
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/impl/path-impl.ts
|
|
71
|
+
var TemplatePathCtor;
|
|
72
|
+
function setTemplatePathCtor(ctor) {
|
|
73
|
+
TemplatePathCtor = ctor;
|
|
74
|
+
}
|
|
75
|
+
var PathImpl = class _PathImpl {
|
|
76
|
+
segments;
|
|
77
|
+
constructor(segments) {
|
|
78
|
+
this.segments = segments;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The number of segments in this path.
|
|
82
|
+
*/
|
|
83
|
+
get length() {
|
|
84
|
+
return this.segments.length;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The string representation of the path (e.g. "users.0.name").
|
|
88
|
+
* Useful for binding paths to form libraries or UI components.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* path<Root>().users[0].name.$; // "users.0.name"
|
|
92
|
+
*/
|
|
93
|
+
get $() {
|
|
94
|
+
return this.toString();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Returns the string representation of the path (e.g. "users.0.name").
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* path<Root>().users[0].name.toString(); // "users.0.name"
|
|
101
|
+
*/
|
|
102
|
+
toString() {
|
|
103
|
+
return this.segments.join(".");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Extracts the value at this path from the given data object.
|
|
107
|
+
* Safely handles missing intermediate properties by returning `undefined` instead of throwing an error.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* const namePath = path<User>().name;
|
|
111
|
+
* const name = namePath.get({ name: "Alice" }); // "Alice"
|
|
112
|
+
*/
|
|
113
|
+
get(data) {
|
|
114
|
+
let current = data;
|
|
115
|
+
for (const seg of this.segments) {
|
|
116
|
+
if (current == null) return void 0;
|
|
117
|
+
current = current[seg];
|
|
118
|
+
}
|
|
119
|
+
return current;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Returns an accessor function that extracts the value at this path from the given data object.
|
|
123
|
+
* Useful for array methods like `.map()` or `.filter()`.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const names = users.map(path<User>().name.fn);
|
|
127
|
+
*/
|
|
128
|
+
get fn() {
|
|
129
|
+
return (data) => this.get(data);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sets the value at this path in the given data object, returning a new updated object (immutable).
|
|
133
|
+
* If intermediate properties are missing, they are automatically created as objects or arrays
|
|
134
|
+
* depending on the segment types (numeric keys become arrays).
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* const namePath = path<User>().name;
|
|
138
|
+
* const updatedUser = namePath.set({ name: "Alice" }, "Bob"); // { name: "Bob" }
|
|
139
|
+
*/
|
|
140
|
+
set(data, value) {
|
|
141
|
+
if (this.segments.length === 0) return value;
|
|
142
|
+
const setAt = (obj, segs, val) => {
|
|
143
|
+
if (segs.length === 1) {
|
|
144
|
+
const key = segs[0];
|
|
145
|
+
if (Array.isArray(obj)) {
|
|
146
|
+
const arr = [...obj];
|
|
147
|
+
arr[key] = val;
|
|
148
|
+
return arr;
|
|
149
|
+
}
|
|
150
|
+
return { ...obj, [key]: val };
|
|
151
|
+
}
|
|
152
|
+
const [first, ...rest] = segs;
|
|
153
|
+
const baseObj2 = obj;
|
|
154
|
+
const next = baseObj2[first];
|
|
155
|
+
const nextCopy = next != null && typeof next === "object" ? Array.isArray(next) ? [...next] : { ...next } : typeof rest[0] === "number" ? [] : {};
|
|
156
|
+
if (Array.isArray(baseObj2)) {
|
|
157
|
+
const arr = [...baseObj2];
|
|
158
|
+
arr[first] = setAt(nextCopy, rest, val);
|
|
159
|
+
return arr;
|
|
160
|
+
}
|
|
161
|
+
return { ...baseObj2, [first]: setAt(nextCopy, rest, val) };
|
|
162
|
+
};
|
|
163
|
+
const baseObj = typeof data === "object" && data !== null ? Array.isArray(data) ? [...data] : { ...data } : data;
|
|
164
|
+
return setAt(baseObj, this.segments, value);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Traverses into a collection (Array or Record) to operate on each item.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* const users = path<Root>().users;
|
|
171
|
+
* const userNames = users.each(u => u.name); // Path matches all names
|
|
172
|
+
*/
|
|
173
|
+
each(expr) {
|
|
174
|
+
let tailSegments = [];
|
|
175
|
+
if (expr) {
|
|
176
|
+
const proxy = createPathProxy([]);
|
|
177
|
+
const result = expr(proxy);
|
|
178
|
+
tailSegments = result?.[PATH_SEGMENTS] ?? [];
|
|
179
|
+
}
|
|
180
|
+
return new TemplatePathCtor([...this.segments, WILDCARD, ...tailSegments]);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Traverses deeply into a structure, matching any nested property.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* const root = path<Root>();
|
|
187
|
+
* const allIds = root.deep(node => node.id); // Path matches any 'id' at any depth
|
|
188
|
+
*/
|
|
189
|
+
deep(expr) {
|
|
190
|
+
let tailSegments = [];
|
|
191
|
+
if (expr) {
|
|
192
|
+
const proxy = createPathProxy([]);
|
|
193
|
+
const result = expr(proxy);
|
|
194
|
+
tailSegments = result?.[PATH_SEGMENTS] ?? [];
|
|
195
|
+
}
|
|
196
|
+
return new TemplatePathCtor([
|
|
197
|
+
...this.segments,
|
|
198
|
+
DEEP_WILDCARD,
|
|
199
|
+
...tailSegments
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Checks if this path starts with the segments of another path.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* const a = path<Root>().users[0].name;
|
|
207
|
+
* const b = path<Root>().users;
|
|
208
|
+
* a.startsWith(b); // true
|
|
209
|
+
*/
|
|
210
|
+
startsWith(other) {
|
|
211
|
+
return matchesPrefix(this.segments, resolveSegments(other));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Checks if this path encompasses the segments of another path (i.e., this path is a prefix of the other).
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* const a = path<Root>().users;
|
|
218
|
+
* const b = path<Root>().users[0].name;
|
|
219
|
+
* a.includes(b); // true
|
|
220
|
+
*/
|
|
221
|
+
includes(other) {
|
|
222
|
+
return matchesPrefix(resolveSegments(other), this.segments);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Checks if this path is exactly equal to another path.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* const a = path<Root>().users;
|
|
229
|
+
* const b = path<Root>().users;
|
|
230
|
+
* a.equals(b); // true
|
|
231
|
+
*/
|
|
232
|
+
equals(other) {
|
|
233
|
+
return segmentsEqual(this.segments, resolveSegments(other));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Matches this path against another path, returning their relationship.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* const a = path<Root>().users[0];
|
|
240
|
+
* const b = path<Root>().users;
|
|
241
|
+
* a.match(b); // { relation: 'child', params: {} }
|
|
242
|
+
*/
|
|
243
|
+
match(other) {
|
|
244
|
+
const otherSegs = resolveSegments(other);
|
|
245
|
+
if (segmentsEqual(this.segments, otherSegs)) {
|
|
246
|
+
return { relation: "equals", params: {} };
|
|
247
|
+
}
|
|
248
|
+
if (matchesPrefix(this.segments, otherSegs) && this.segments.length > otherSegs.length) {
|
|
249
|
+
return { relation: "child", params: {} };
|
|
250
|
+
}
|
|
251
|
+
if (matchesPrefix(otherSegs, this.segments) && otherSegs.length > this.segments.length) {
|
|
252
|
+
return { relation: "parent", params: {} };
|
|
253
|
+
}
|
|
254
|
+
if (patternMatches(this.segments, otherSegs)) {
|
|
255
|
+
return { relation: "includes", params: {} };
|
|
256
|
+
}
|
|
257
|
+
if (patternMatches(otherSegs, this.segments)) {
|
|
258
|
+
return { relation: "included-by", params: {} };
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Appends another path to the end of this path. If the end of this path matches
|
|
264
|
+
* the beginning of the other path, the overlapping segments are intelligently deduplicated.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* const base = path<Root>().users;
|
|
268
|
+
* const full = base.merge(p => p[0].name); // equivalent to path<Root>().users[0].name
|
|
269
|
+
*/
|
|
270
|
+
merge(other) {
|
|
271
|
+
const a = this.segments;
|
|
272
|
+
const b = resolveSegments(other);
|
|
273
|
+
let overlapLen = 0;
|
|
274
|
+
for (let len = Math.min(a.length, b.length); len >= 1; len--) {
|
|
275
|
+
const aSuffix = a.slice(-len);
|
|
276
|
+
const bPrefix = b.slice(0, len);
|
|
277
|
+
if (segmentsEqual(aSuffix, bPrefix)) {
|
|
278
|
+
overlapLen = len;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const merged = overlapLen > 0 ? [...a.slice(0, -overlapLen), ...b] : [...a, ...b];
|
|
283
|
+
return new _PathImpl(merged);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Removes the segments of another path from either the beginning or the end of this path.
|
|
287
|
+
* Returns `null` if the other path is neither a prefix nor a suffix.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* const full = path<Root>().users[0].name;
|
|
291
|
+
* const base = path<Root>().users;
|
|
292
|
+
* const remainder = full.subtract(base); // equivalent to path()[0].name
|
|
293
|
+
*/
|
|
294
|
+
subtract(other) {
|
|
295
|
+
const a = this.segments;
|
|
296
|
+
const b = resolveSegments(other);
|
|
297
|
+
if (b.length > a.length) return null;
|
|
298
|
+
if (segmentsEqual(a, b)) return new _PathImpl([]);
|
|
299
|
+
if (segmentsEqual(a.slice(0, b.length), b)) {
|
|
300
|
+
return new _PathImpl(a.slice(b.length));
|
|
301
|
+
}
|
|
302
|
+
if (segmentsEqual(a.slice(-b.length), b)) {
|
|
303
|
+
return new _PathImpl(a.slice(0, -b.length));
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Returns a new path containing a subset of the segments, similar to Array.prototype.slice.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* const full = path<Root>().users[0].name;
|
|
312
|
+
* full.slice(0, 1); // equivalent to path<Root>().users
|
|
313
|
+
*/
|
|
314
|
+
slice(start, end) {
|
|
315
|
+
const s = this.segments.slice(start, end);
|
|
316
|
+
return new _PathImpl(s);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Extends the current path using a lambda expression starting from the resolved value.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* const userPath = path<Root>().users[0];
|
|
323
|
+
* const namePath = userPath.to(u => u.name);
|
|
324
|
+
*/
|
|
325
|
+
to(expr) {
|
|
326
|
+
const proxy = createPathProxy([]);
|
|
327
|
+
const result = expr(proxy);
|
|
328
|
+
const tailSegments = result?.[PATH_SEGMENTS] ?? [];
|
|
329
|
+
return new _PathImpl([...this.segments, ...tailSegments]);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/impl/template-path-impl.ts
|
|
334
|
+
var TemplatePathImpl = class _TemplatePathImpl extends PathImpl {
|
|
335
|
+
/**
|
|
336
|
+
* Traverses into a collection (Array or Record) to operate on each item, returning a TemplatePath.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* const users = path<Root>().users;
|
|
340
|
+
* const userNames = users.each(u => u.name); // TemplatePath matching all names
|
|
341
|
+
*/
|
|
342
|
+
each(expr) {
|
|
343
|
+
let tailSegments = [];
|
|
344
|
+
if (expr) {
|
|
345
|
+
const proxy = createPathProxy([]);
|
|
346
|
+
const result = expr(proxy);
|
|
347
|
+
tailSegments = result?.[PATH_SEGMENTS] ?? [];
|
|
348
|
+
}
|
|
349
|
+
return new _TemplatePathImpl([
|
|
350
|
+
...this.segments,
|
|
351
|
+
WILDCARD,
|
|
352
|
+
...tailSegments
|
|
353
|
+
]);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Traverses deeply into a structure, matching any nested property, returning a TemplatePath.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* const root = path<Root>();
|
|
360
|
+
* const allIds = root.deep(node => node.id); // TemplatePath matching any 'id' at any depth
|
|
361
|
+
*/
|
|
362
|
+
deep(expr) {
|
|
363
|
+
let tailSegments = [];
|
|
364
|
+
if (expr) {
|
|
365
|
+
const proxy = createPathProxy([]);
|
|
366
|
+
const result = expr(proxy);
|
|
367
|
+
tailSegments = result?.[PATH_SEGMENTS] ?? [];
|
|
368
|
+
}
|
|
369
|
+
return new _TemplatePathImpl([
|
|
370
|
+
...this.segments,
|
|
371
|
+
DEEP_WILDCARD,
|
|
372
|
+
...tailSegments
|
|
373
|
+
]);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Resolves this template path against actual data to return an array of concrete paths
|
|
377
|
+
* that exist in the given data.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* const template = path<Root>().users.each().name;
|
|
381
|
+
* const concretePaths = template.expand(data); // [path<Root>().users[0].name, ...]
|
|
382
|
+
*/
|
|
383
|
+
expand(data) {
|
|
384
|
+
const results = [];
|
|
385
|
+
const walk = (currentData, segmentIdx, currentPath) => {
|
|
386
|
+
if (segmentIdx >= this.segments.length) {
|
|
387
|
+
results.push(new PathImpl(currentPath));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const seg = this.segments[segmentIdx];
|
|
391
|
+
if (seg === WILDCARD) {
|
|
392
|
+
if (currentData != null && typeof currentData === "object") {
|
|
393
|
+
const keys = Array.isArray(currentData) ? Array.from(currentData.keys()) : Object.keys(currentData);
|
|
394
|
+
for (const key of keys) {
|
|
395
|
+
walk(
|
|
396
|
+
currentData[key],
|
|
397
|
+
segmentIdx + 1,
|
|
398
|
+
[...currentPath, key]
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} else if (seg === DEEP_WILDCARD) {
|
|
403
|
+
walk(currentData, segmentIdx + 1, currentPath);
|
|
404
|
+
if (currentData != null && typeof currentData === "object") {
|
|
405
|
+
const keys = Array.isArray(currentData) ? Array.from(currentData.keys()) : Object.keys(currentData);
|
|
406
|
+
for (const key of keys) {
|
|
407
|
+
walk(
|
|
408
|
+
currentData[key],
|
|
409
|
+
segmentIdx,
|
|
410
|
+
[...currentPath, key]
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
if (currentData != null && typeof currentData === "object" && seg in currentData) {
|
|
416
|
+
walk(
|
|
417
|
+
currentData[seg],
|
|
418
|
+
segmentIdx + 1,
|
|
419
|
+
[...currentPath, seg]
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
walk(data, 0, []);
|
|
425
|
+
return results;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Extracts an array of values at this template path from the given data object.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* const names = path<Root>().users.each().name.get(data); // string[]
|
|
432
|
+
*/
|
|
433
|
+
// @ts-expect-error Overriding get to return an array instead of a single value
|
|
434
|
+
get(data) {
|
|
435
|
+
return this.expand(data).map((p) => p.get(data));
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Returns an accessor function that extracts an array of values at this template path from the given data object.
|
|
439
|
+
* Useful for array methods like `.map()` or `.filter()`.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* const allNames = companies.map(path<Company>().departments.each().name.fn);
|
|
443
|
+
*/
|
|
444
|
+
// @ts-expect-error Overriding fn to return an array instead of a single value
|
|
445
|
+
get fn() {
|
|
446
|
+
return (data) => this.get(data);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Sets the provided value to all matching paths immutably.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* const updatedData = path<Root>().users.each().name.set(data, "Bob");
|
|
453
|
+
*/
|
|
454
|
+
set(data, value) {
|
|
455
|
+
const paths = this.expand(data);
|
|
456
|
+
let current = data;
|
|
457
|
+
for (const p of paths) {
|
|
458
|
+
current = p.set(current, value);
|
|
459
|
+
}
|
|
460
|
+
return current;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// src/path.ts
|
|
465
|
+
setTemplatePathCtor(TemplatePathImpl);
|
|
466
|
+
function path(baseOrExpr, expr) {
|
|
467
|
+
if (!baseOrExpr) {
|
|
468
|
+
return new PathImpl([]);
|
|
469
|
+
}
|
|
470
|
+
if (typeof baseOrExpr === "function") {
|
|
471
|
+
const proxy = createPathProxy([]);
|
|
472
|
+
const result = baseOrExpr(proxy);
|
|
473
|
+
const segments = result?.[PATH_SEGMENTS] ?? [];
|
|
474
|
+
return new PathImpl(segments);
|
|
475
|
+
}
|
|
476
|
+
const baseSegments = baseOrExpr.segments;
|
|
477
|
+
if (expr) {
|
|
478
|
+
if (typeof expr === "function") {
|
|
479
|
+
const proxy = createPathProxy([]);
|
|
480
|
+
const result = expr(proxy);
|
|
481
|
+
const tailSegments = result?.[PATH_SEGMENTS] ?? [];
|
|
482
|
+
return new PathImpl([...baseSegments, ...tailSegments]);
|
|
483
|
+
} else if (typeof expr === "object" && "segments" in expr) {
|
|
484
|
+
return new PathImpl([
|
|
485
|
+
...baseSegments,
|
|
486
|
+
...expr.segments
|
|
487
|
+
]);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return new PathImpl(baseSegments);
|
|
491
|
+
}
|
|
492
|
+
function unsafePath(raw) {
|
|
493
|
+
const segments = raw ? raw.split(".").map((s) => s === "" ? s : isCanonicalArrayIndex(s) ? Number(s) : s) : [];
|
|
494
|
+
return new PathImpl(segments);
|
|
495
|
+
}
|
|
496
|
+
export {
|
|
497
|
+
path,
|
|
498
|
+
unsafePath
|
|
499
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "data-path",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A simple, modern, zero-dependency, and fully type-safe library for building, comparing, and manipulating object property paths using TypeScript lambda expressions.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
15
|
+
"dev": "npm run build -- --watch",
|
|
16
|
+
"lint": "biome check src",
|
|
17
|
+
"lint:fix": "biome check --write src",
|
|
18
|
+
"prepare": "husky",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"test": "vitest run --typecheck",
|
|
21
|
+
"test:watch": "vitest --typecheck"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"typescript",
|
|
25
|
+
"path",
|
|
26
|
+
"object-path",
|
|
27
|
+
"type-safe",
|
|
28
|
+
"proxy",
|
|
29
|
+
"dot-notation",
|
|
30
|
+
"property-path",
|
|
31
|
+
"accessor",
|
|
32
|
+
"data-path"
|
|
33
|
+
],
|
|
34
|
+
"author": {
|
|
35
|
+
"name": "Sergei Shmakov",
|
|
36
|
+
"url": "https://github.com/sergeyshmakov"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/sergeyshmakov/data-path.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/sergeyshmakov/data-path/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/sergeyshmakov/data-path#readme",
|
|
46
|
+
"release": {
|
|
47
|
+
"branches": [
|
|
48
|
+
"main"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@biomejs/biome": "2.4.6",
|
|
53
|
+
"@commitlint/cli": "^20.4.3",
|
|
54
|
+
"@commitlint/config-conventional": "^20.4.3",
|
|
55
|
+
"husky": "^9.1.7",
|
|
56
|
+
"semantic-release": "^25.0.3",
|
|
57
|
+
"tsup": "^8.5.1",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vitest": "^4.0.18"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"typescript": ">=5.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"typescript": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=20.0.0"
|
|
71
|
+
},
|
|
72
|
+
"volta": {
|
|
73
|
+
"node": "24.14.0"
|
|
74
|
+
}
|
|
75
|
+
}
|