1- import type { Segment , TemplatePath , Path , CollectionItem , DeepReachable } from "../types.js" ;
2- import { WILDCARD , DEEP_WILDCARD , PATH_SEGMENTS } from "../constants.js" ;
1+ import { DEEP_WILDCARD , PATH_SEGMENTS , WILDCARD } from "../constants.js" ;
2+ import type {
3+ CollectionItem ,
4+ DeepReachable ,
5+ Path ,
6+ Segment ,
7+ TemplatePath ,
8+ } from "../types.js" ;
39import { createPathProxy } from "../utils.js" ;
410import { PathImpl } from "./path-impl.js" ;
511
6- export class TemplatePathImpl < T = unknown , V = unknown , P extends string = string >
7- extends PathImpl < T , V , P >
8- {
9- /**
10- * Traverses into a collection (Array or Record) to operate on each item, returning a TemplatePath.
11- *
12- * @example
13- * const users = path<Root>().users;
14- * const userNames = users.each(u => u.name); // TemplatePath matching all names
15- */
16- each < U = CollectionItem < V > > (
17- expr ?: ( item : CollectionItem < V > ) => U ,
18- ) : TemplatePath < T , U , `${string } .${"*" } .${string } `> {
19- let tailSegments : readonly Segment [ ] = [ ] ;
20- if ( expr ) {
21- const proxy = createPathProxy ( [ ] ) ;
22- const result = expr ( proxy as CollectionItem < V > ) ;
23- tailSegments = ( result as Record < symbol , unknown > ) ?. [ PATH_SEGMENTS ] as Segment [ ] ?? [ ] ;
24- }
25- return new TemplatePathImpl ( [
26- ...this . segments ,
27- WILDCARD ,
28- ...tailSegments ,
29- ] ) as unknown as TemplatePath < T , U , `${string } .${"*" } .${string } `> ;
30- }
12+ export class TemplatePathImpl <
13+ T = unknown ,
14+ V = unknown ,
15+ P extends string = string ,
16+ > extends PathImpl < T , V , P > {
17+ /**
18+ * Traverses into a collection (Array or Record) to operate on each item, returning a TemplatePath.
19+ *
20+ * @example
21+ * const users = path<Root>().users;
22+ * const userNames = users.each(u => u.name); // TemplatePath matching all names
23+ */
24+ each < U = CollectionItem < V > > (
25+ expr ?: ( item : CollectionItem < V > ) => U ,
26+ ) : TemplatePath < T , U , `${string } .${"*" } .${string } `> {
27+ let tailSegments : readonly Segment [ ] = [ ] ;
28+ if ( expr ) {
29+ const proxy = createPathProxy ( [ ] ) ;
30+ const result = expr ( proxy as CollectionItem < V > ) ;
31+ tailSegments =
32+ ( ( result as Record < symbol , unknown > ) ?. [ PATH_SEGMENTS ] as Segment [ ] ) ??
33+ [ ] ;
34+ }
35+ return new TemplatePathImpl ( [
36+ ...this . segments ,
37+ WILDCARD ,
38+ ...tailSegments ,
39+ ] ) as unknown as TemplatePath < T , U , `${string } .${"*" } .${string } `> ;
40+ }
3141
32- /**
33- * Traverses deeply into a structure, matching any nested property, returning a TemplatePath.
34- *
35- * @example
36- * const root = path<Root>();
37- * const allIds = root.deep(node => node.id); // TemplatePath matching any 'id' at any depth
38- */
39- deep < U = DeepReachable < V > > (
40- expr ?: ( leaf : DeepReachable < V > ) => U ,
41- ) : TemplatePath < T , U , `${string } .${"**" } .${string } `> {
42- let tailSegments : readonly Segment [ ] = [ ] ;
43- if ( expr ) {
44- const proxy = createPathProxy ( [ ] ) ;
45- const result = expr ( proxy as DeepReachable < V > ) ;
46- tailSegments = ( result as Record < symbol , unknown > ) ?. [ PATH_SEGMENTS ] as Segment [ ] ?? [ ] ;
47- }
48- return new TemplatePathImpl ( [
49- ...this . segments ,
50- DEEP_WILDCARD ,
51- ...tailSegments ,
52- ] ) as unknown as TemplatePath < T , U , `${string } .${"**" } .${string } `> ;
53- }
42+ /**
43+ * Traverses deeply into a structure, matching any nested property, returning a TemplatePath.
44+ *
45+ * @example
46+ * const root = path<Root>();
47+ * const allIds = root.deep(node => node.id); // TemplatePath matching any 'id' at any depth
48+ */
49+ deep < U = DeepReachable < V > > (
50+ expr ?: ( leaf : DeepReachable < V > ) => U ,
51+ ) : TemplatePath < T , U , `${string } .${"**" } .${string } `> {
52+ let tailSegments : readonly Segment [ ] = [ ] ;
53+ if ( expr ) {
54+ const proxy = createPathProxy ( [ ] ) ;
55+ const result = expr ( proxy as DeepReachable < V > ) ;
56+ tailSegments =
57+ ( ( result as Record < symbol , unknown > ) ?. [ PATH_SEGMENTS ] as Segment [ ] ) ??
58+ [ ] ;
59+ }
60+ return new TemplatePathImpl ( [
61+ ...this . segments ,
62+ DEEP_WILDCARD ,
63+ ...tailSegments ,
64+ ] ) as unknown as TemplatePath < T , U , `${string } .${"**" } .${string } `> ;
65+ }
5466
55- /**
56- * Resolves this template path against actual data to return an array of concrete paths
57- * that exist in the given data.
58- *
59- * @example
60- * const template = path<Root>().users.each().name;
61- * const concretePaths = template.expand(data); // [path<Root>().users[0].name, ...]
62- */
63- expand ( data : T ) : Path < T , V , string > [ ] {
64- const results : Path < T , V , string > [ ] = [ ] ;
65-
66- const walk = ( currentData : unknown , segmentIdx : number , currentPath : Segment [ ] ) => {
67- if ( segmentIdx >= this . segments . length ) {
68- results . push ( new PathImpl < T , V , string > ( currentPath ) ) ;
69- return ;
70- }
67+ /**
68+ * Resolves this template path against actual data to return an array of concrete paths
69+ * that exist in the given data.
70+ *
71+ * @example
72+ * const template = path<Root>().users.each().name;
73+ * const concretePaths = template.expand(data); // [path<Root>().users[0].name, ...]
74+ */
75+ expand ( data : T ) : Path < T , V , string > [ ] {
76+ const results : Path < T , V , string > [ ] = [ ] ;
7177
72- const seg = this . segments [ segmentIdx ] ;
78+ const walk = (
79+ currentData : unknown ,
80+ segmentIdx : number ,
81+ currentPath : Segment [ ] ,
82+ ) => {
83+ if ( segmentIdx >= this . segments . length ) {
84+ results . push ( new PathImpl < T , V , string > ( currentPath ) ) ;
85+ return ;
86+ }
7387
74- if ( seg === WILDCARD ) {
75- if ( currentData != null && typeof currentData === "object" ) {
76- const keys = Array . isArray ( currentData )
77- ? Array . from ( currentData . keys ( ) )
78- : Object . keys ( currentData ) ;
79- for ( const key of keys ) {
80- walk ( ( currentData as Record < string | number , unknown > ) [ key ] , segmentIdx + 1 , [ ...currentPath , key ] ) ;
81- }
82- }
83- } else if ( seg === DEEP_WILDCARD ) {
84- // Match the rest of the path on the current node
85- walk ( currentData , segmentIdx + 1 , currentPath ) ;
88+ const seg = this . segments [ segmentIdx ] ;
8689
87- // Recursively explore all descendants
88- if ( currentData != null && typeof currentData === "object" ) {
89- const keys = Array . isArray ( currentData )
90- ? Array . from ( currentData . keys ( ) )
91- : Object . keys ( currentData ) ;
92- for ( const key of keys ) {
93- walk ( ( currentData as Record < string | number , unknown > ) [ key ] , segmentIdx , [ ...currentPath , key ] ) ;
94- }
95- }
96- } else {
97- if ( currentData != null && typeof currentData === "object" && seg in currentData ) {
98- walk ( ( currentData as Record < string | number , unknown > ) [ seg ] , segmentIdx + 1 , [ ...currentPath , seg ] ) ;
99- }
100- }
101- } ;
90+ if ( seg === WILDCARD ) {
91+ if ( currentData != null && typeof currentData === "object" ) {
92+ const keys = Array . isArray ( currentData )
93+ ? Array . from ( currentData . keys ( ) )
94+ : Object . keys ( currentData ) ;
95+ for ( const key of keys ) {
96+ walk (
97+ ( currentData as Record < string | number , unknown > ) [ key ] ,
98+ segmentIdx + 1 ,
99+ [ ...currentPath , key ] ,
100+ ) ;
101+ }
102+ }
103+ } else if ( seg === DEEP_WILDCARD ) {
104+ // Match the rest of the path on the current node
105+ walk ( currentData , segmentIdx + 1 , currentPath ) ;
102106
103- walk ( data , 0 , [ ] ) ;
104- return results ;
105- }
107+ // Recursively explore all descendants
108+ if ( currentData != null && typeof currentData === "object" ) {
109+ const keys = Array . isArray ( currentData )
110+ ? Array . from ( currentData . keys ( ) )
111+ : Object . keys ( currentData ) ;
112+ for ( const key of keys ) {
113+ walk (
114+ ( currentData as Record < string | number , unknown > ) [ key ] ,
115+ segmentIdx ,
116+ [ ...currentPath , key ] ,
117+ ) ;
118+ }
119+ }
120+ } else {
121+ if (
122+ currentData != null &&
123+ typeof currentData === "object" &&
124+ seg in currentData
125+ ) {
126+ walk (
127+ ( currentData as Record < string | number , unknown > ) [ seg ] ,
128+ segmentIdx + 1 ,
129+ [ ...currentPath , seg ] ,
130+ ) ;
131+ }
132+ }
133+ } ;
106134
107- /**
108- * Extracts an array of values at this template path from the given data object.
109- *
110- * @example
111- * const names = path<Root>().users.each().name.get(data); // string[]
112- */
113- // @ts -expect-error Overriding get to return an array instead of a single value
114- get ( data : T ) : V [ ] {
115- return this . expand ( data ) . map ( p => p . get ( data ) ) ;
116- }
135+ walk ( data , 0 , [ ] ) ;
136+ return results ;
137+ }
117138
118- /**
119- * Returns an accessor function that extracts an array of values at this template path from the given data object.
120- * Useful for array methods like `.map()` or `.filter()`.
121- *
122- * @example
123- * const allNames = companies.map(path<Company>().departments.each().name.fn);
124- */
125- // @ts -expect-error Overriding fn to return an array instead of a single value
126- get fn ( ) : ( data : T ) => V [ ] {
127- return ( data : T ) => this . get ( data ) ;
128- }
139+ /**
140+ * Extracts an array of values at this template path from the given data object.
141+ *
142+ * @example
143+ * const names = path<Root>().users.each().name.get(data); // string[]
144+ */
145+ // @ts -expect-error Overriding get to return an array instead of a single value
146+ get ( data : T ) : V [ ] {
147+ return this . expand ( data ) . map ( ( p ) => p . get ( data ) ) ;
148+ }
129149
130- /**
131- * Sets the provided value to all matching paths immutably.
132- *
133- * @example
134- * const updatedData = path<Root>().users.each().name.set(data, "Bob");
135- */
136- set ( data : T , value : V ) : T {
137- const paths = this . expand ( data ) ;
138- let current = data ;
139- for ( const p of paths ) {
140- current = p . set ( current , value ) ;
141- }
142- return current ;
143- }
144- }
150+ /**
151+ * Returns an accessor function that extracts an array of values at this template path from the given data object.
152+ * Useful for array methods like `.map()` or `.filter()`.
153+ *
154+ * @example
155+ * const allNames = companies.map(path<Company>().departments.each().name.fn);
156+ */
157+ // @ts -expect-error Overriding fn to return an array instead of a single value
158+ get fn ( ) : ( data : T ) => V [ ] {
159+ return ( data : T ) => this . get ( data ) ;
160+ }
161+
162+ /**
163+ * Sets the provided value to all matching paths immutably.
164+ *
165+ * @example
166+ * const updatedData = path<Root>().users.each().name.set(data, "Bob");
167+ */
168+ set ( data : T , value : V ) : T {
169+ const paths = this . expand ( data ) ;
170+ let current = data ;
171+ for ( const p of paths ) {
172+ current = p . set ( current , value ) ;
173+ }
174+ return current ;
175+ }
176+ }
0 commit comments