assign-gingerly 0.0.79 → 0.0.81

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/DX/paths.js CHANGED
@@ -27,6 +27,34 @@
27
27
  * The sp tag function uses this to auto-extract path strings from proxies.
28
28
  */
29
29
  const PATH_SYMBOL = Symbol('assign-gingerly-path');
30
+ function isPathProxy(value) {
31
+ return !!value && (typeof value === 'object' || typeof value === 'function') && PATH_SYMBOL in value;
32
+ }
33
+ const COMMAND_TOKEN_SUFFIXES = {
34
+ Each: '?.@each',
35
+ EqNot: ' =!',
36
+ PlusEq: ' +=',
37
+ QMEq: ' ?=',
38
+ YEq: ' Y=',
39
+ MinusEq: ' -=',
40
+ Arrow: ' =>',
41
+ };
42
+ function serializePath(prefix) {
43
+ return prefix.length > 0 ? `?.${prefix}` : '?.';
44
+ }
45
+ function appendPathSegment(prefix, segment) {
46
+ return prefix ? `${prefix}?.${segment}` : segment;
47
+ }
48
+ function appendCommandSuffix(prefix, token) {
49
+ return `${prefix}${COMMAND_TOKEN_SUFFIXES[token]}`;
50
+ }
51
+ function getReservedToken(prop) {
52
+ if (prop === 'Path' || prop === 'path')
53
+ return prop;
54
+ if (prop in COMMAND_TOKEN_SUFFIXES)
55
+ return prop;
56
+ return undefined;
57
+ }
30
58
  /**
31
59
  * Create a proxy for id-ref paths (#[varName]).
32
60
  * After the initial #[varName], further property access chains with ?. from the resolved element.
@@ -34,15 +62,23 @@ const PATH_SYMBOL = Symbol('assign-gingerly-path');
34
62
  */
35
63
  function createIdRefProxy(idRef, options) {
36
64
  function handler() { }
65
+ Object.defineProperty(handler, PATH_SYMBOL, { value: idRef });
37
66
  return new Proxy(handler, {
38
67
  get(_, prop) {
39
- if (prop === 'path' || prop === PATH_SYMBOL) {
68
+ if (prop === 'path' || prop === 'Path' || prop === PATH_SYMBOL) {
40
69
  return idRef;
41
70
  }
42
71
  if (typeof prop === 'symbol')
43
72
  return undefined;
73
+ const reservedToken = getReservedToken(prop);
74
+ if (reservedToken === 'Each') {
75
+ return createIdRefProxy(appendPathSegment(idRef, '@each'), options);
76
+ }
77
+ if (reservedToken && reservedToken !== 'Path' && reservedToken !== 'path') {
78
+ return createIdRefProxy(appendCommandSuffix(idRef, reservedToken), options);
79
+ }
44
80
  // Chain further path segments after the id ref
45
- const chained = `${idRef}?.${String(prop)}`;
81
+ const chained = appendPathSegment(idRef, String(prop));
46
82
  return createIdRefProxy(chained, options);
47
83
  },
48
84
  apply(_, __, args) {
@@ -53,7 +89,7 @@ function createIdRefProxy(idRef, options) {
53
89
  argStr = 'true';
54
90
  else if (arg === false)
55
91
  argStr = 'false';
56
- else if (arg && typeof arg === 'object' && PATH_SYMBOL in arg) {
92
+ else if (isPathProxy(arg)) {
57
93
  const fullPath = arg[PATH_SYMBOL];
58
94
  argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
59
95
  }
@@ -77,14 +113,25 @@ function createPathProxy(prefix, options) {
77
113
  const aliasMap = options?.aka;
78
114
  // Use a function as the target to enable the apply trap
79
115
  function handler() { }
116
+ Object.defineProperty(handler, PATH_SYMBOL, { value: prefix.length > 0 ? `?.${prefix}` : '?.' });
80
117
  return new Proxy(handler, {
81
118
  get(_, prop) {
82
- if (prop === 'path' || prop === PATH_SYMBOL) {
83
- return prefix.length > 0 ? `?.${prefix}` : '?.';
119
+ if (prop === 'path' || prop === 'Path' || prop === PATH_SYMBOL) {
120
+ return serializePath(prefix);
84
121
  }
85
122
  // Ignore symbol access (Symbol.iterator, Symbol.toPrimitive, etc.)
86
123
  if (typeof prop === 'symbol')
87
124
  return undefined;
125
+ const reservedToken = getReservedToken(String(prop));
126
+ if (reservedToken === 'Path' || reservedToken === 'path') {
127
+ return serializePath(prefix);
128
+ }
129
+ if (reservedToken === 'Each') {
130
+ return createPathProxy(appendPathSegment(prefix, '@each'), options);
131
+ }
132
+ if (reservedToken) {
133
+ return createPathProxy(appendCommandSuffix(prefix, reservedToken), options);
134
+ }
88
135
  let segment = String(prop);
89
136
  // #-prefix: $['#firstName'] → '#[firstName]' (cached element ref)
90
137
  if (segment.startsWith('#')) {
@@ -102,7 +149,7 @@ function createPathProxy(prefix, options) {
102
149
  }
103
150
  }
104
151
  }
105
- const newPath = prefix ? `${prefix}?.${segment}` : segment;
152
+ const newPath = appendPathSegment(prefix, segment);
106
153
  return createPathProxy(newPath, options);
107
154
  },
108
155
  apply(_, __, args) {
@@ -114,14 +161,14 @@ function createPathProxy(prefix, options) {
114
161
  argStr = 'true';
115
162
  else if (arg === false)
116
163
  argStr = 'false';
117
- else if (arg && typeof arg === 'object' && PATH_SYMBOL in arg) {
164
+ else if (isPathProxy(arg)) {
118
165
  // Proxy arg — extract path without '?.' prefix
119
166
  const fullPath = arg[PATH_SYMBOL];
120
167
  argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
121
168
  }
122
169
  else
123
170
  argStr = String(arg);
124
- const newPath = prefix ? `${prefix}?.${argStr}` : argStr;
171
+ const newPath = appendPathSegment(prefix, argStr);
125
172
  return createPathProxy(newPath, options);
126
173
  }
127
174
  // No args — method called with no arguments, return self
@@ -167,12 +214,12 @@ export function paths(options) {
167
214
  * }
168
215
  */
169
216
  export function set(lhs) {
170
- const lhsStr = lhs && typeof lhs === 'object' && PATH_SYMBOL in lhs
217
+ const lhsStr = isPathProxy(lhs)
171
218
  ? lhs[PATH_SYMBOL]
172
219
  : String(lhs);
173
220
  return {
174
221
  to(rhs) {
175
- const rhsStr = rhs && typeof rhs === 'object' && PATH_SYMBOL in rhs
222
+ const rhsStr = isPathProxy(rhs)
176
223
  ? rhs[PATH_SYMBOL]
177
224
  : rhs;
178
225
  return { [lhsStr]: rhsStr };
@@ -198,7 +245,7 @@ export function set(lhs) {
198
245
  * // { assign: { incrementButton: '?.clone?.q?..increment', ... } }
199
246
  */
200
247
  export function smoothOver(value) {
201
- if (value && typeof value === 'object' && PATH_SYMBOL in value) {
248
+ if (isPathProxy(value)) {
202
249
  return value[PATH_SYMBOL];
203
250
  }
204
251
  if (Array.isArray(value)) {
@@ -294,13 +341,13 @@ export function sp(strings, ...values) {
294
341
  result.push(strings[i]);
295
342
  if (i < values.length) {
296
343
  const v = values[i];
297
- if (v && typeof v === 'object' && PATH_SYMBOL in v) {
344
+ if (isPathProxy(v)) {
298
345
  // Auto-extract path from proxy object
299
346
  result.push(v[PATH_SYMBOL]);
300
347
  }
301
348
  else if (Array.isArray(v)) {
302
349
  // Nested array — recursively extract paths from proxy elements
303
- result.push(v.map(el => el && typeof el === 'object' && PATH_SYMBOL in el ? el[PATH_SYMBOL] : el));
350
+ result.push(v.map(el => isPathProxy(el) ? el[PATH_SYMBOL] : el));
304
351
  }
305
352
  else {
306
353
  result.push(v);
@@ -314,8 +361,10 @@ export function sp(strings, ...values) {
314
361
  * e.g., '?.address?.city' → 'city', '?.firstName' → 'firstName'
315
362
  */
316
363
  function extractPropName(pathStr) {
317
- const parts = pathStr.split('?.');
318
- return parts[parts.length - 1];
364
+ const withoutCommand = pathStr.replace(/(?: \+=| =!| \?=| Y=| -=| =>)$/, '');
365
+ const parts = withoutCommand.split('?.');
366
+ const last = parts[parts.length - 1];
367
+ return last === '@each' ? 'Each' : last;
319
368
  }
320
369
  /**
321
370
  * Tagged template literal that produces an array of {prop, val} objects + literal strings.
@@ -349,7 +398,7 @@ export function md(strings, ...values) {
349
398
  result.push(strings[i]);
350
399
  if (i < values.length) {
351
400
  const v = values[i];
352
- if (v && typeof v === 'object' && PATH_SYMBOL in v) {
401
+ if (isPathProxy(v)) {
353
402
  // Proxy object → {prop, val}
354
403
  const pathStr = v[PATH_SYMBOL];
355
404
  result.push({ prop: extractPropName(pathStr), val: pathStr });
@@ -357,7 +406,7 @@ export function md(strings, ...values) {
357
406
  else if (Array.isArray(v)) {
358
407
  // Nested array — recursively convert proxy elements to {prop, val}
359
408
  result.push(v.map(el => {
360
- if (el && typeof el === 'object' && PATH_SYMBOL in el) {
409
+ if (isPathProxy(el)) {
361
410
  const pathStr = el[PATH_SYMBOL];
362
411
  return { prop: extractPropName(pathStr), val: pathStr };
363
412
  }
@@ -367,7 +416,7 @@ export function md(strings, ...values) {
367
416
  else if (v && typeof v === 'object' && 'prop' in v) {
368
417
  // Developer override object — extract val from proxy if present
369
418
  const processed = { ...v };
370
- if (processed.val && typeof processed.val === 'object' && PATH_SYMBOL in processed.val) {
419
+ if (isPathProxy(processed.val)) {
371
420
  processed.val = processed.val[PATH_SYMBOL];
372
421
  }
373
422
  result.push(processed);
package/DX/paths.ts CHANGED
@@ -27,21 +27,68 @@
27
27
  * Symbol used internally to detect path proxy objects.
28
28
  * The sp tag function uses this to auto-extract path strings from proxies.
29
29
  */
30
- const PATH_SYMBOL = Symbol('assign-gingerly-path');
30
+ const PATH_SYMBOL = Symbol('assign-gingerly-path');
31
+
32
+ function isPathProxy(value: unknown): value is { [PATH_SYMBOL]: string } {
33
+ return !!value && (typeof value === 'object' || typeof value === 'function') && PATH_SYMBOL in value;
34
+ }
35
+
36
+ const COMMAND_TOKEN_SUFFIXES = {
37
+ Each: '?.@each',
38
+ EqNot: ' =!',
39
+ PlusEq: ' +=',
40
+ QMEq: ' ?=',
41
+ YEq: ' Y=',
42
+ MinusEq: ' -=',
43
+ Arrow: ' =>',
44
+ } as const;
45
+
46
+ type CommandToken = keyof typeof COMMAND_TOKEN_SUFFIXES;
47
+
48
+ function serializePath(prefix: string): string {
49
+ return prefix.length > 0 ? `?.${prefix}` : '?.';
50
+ }
51
+
52
+ function appendPathSegment(prefix: string, segment: string): string {
53
+ return prefix ? `${prefix}?.${segment}` : segment;
54
+ }
55
+
56
+ function appendCommandSuffix(prefix: string, token: CommandToken): string {
57
+ return `${prefix}${COMMAND_TOKEN_SUFFIXES[token]}`;
58
+ }
59
+
60
+ function getReservedToken(prop: string): CommandToken | 'Path' | 'path' | undefined {
61
+ if (prop === 'Path' || prop === 'path') return prop;
62
+ if (prop in COMMAND_TOKEN_SUFFIXES) return prop as CommandToken;
63
+ return undefined;
64
+ }
31
65
 
32
66
  /**
33
- * Type that maps an object type to a proxy where every property access
34
- * returns either a deeper proxy (for object properties) or a terminal
35
- * with a `.path` string accessor — while providing full autocomplete.
36
- * Enhanced: also callable (for method call syntax).
37
- */
38
- export type PathProxy<T> = {
39
- [K in keyof T]-?: T[K] extends ((...args: any[]) => infer R)
40
- ? ((...args: any[]) => PathProxy<NonNullable<R>> & { readonly path: string }) & PathProxy<NonNullable<R>> & { readonly path: string }
41
- : T[K] extends (object | undefined | null)
42
- ? PathProxy<NonNullable<T[K]>> & { readonly path: string } & ((...args: any[]) => PathProxy<any> & { readonly path: string })
43
- : { readonly path: string } & ((...args: any[]) => PathProxy<any> & { readonly path: string });
44
- } & { readonly path: string } & ((...args: any[]) => PathProxy<any> & { readonly path: string });
67
+ * Type that maps an object type to a proxy where every property access
68
+ * returns either a deeper proxy (for object properties) or a terminal
69
+ * with a serialized path string accessor — while providing full autocomplete.
70
+ * Enhanced: also callable (for method call syntax) and includes reserved
71
+ * command markers such as `Each`, `EqNot`, and `PlusEq`.
72
+ */
73
+ export type PathProxyCore = {
74
+ readonly Path: string;
75
+ readonly path: string;
76
+ readonly Each: PathProxy<any>;
77
+ readonly EqNot: PathProxy<any>;
78
+ readonly PlusEq: PathProxy<any>;
79
+ readonly QMEq: PathProxy<any>;
80
+ readonly YEq: PathProxy<any>;
81
+ readonly MinusEq: PathProxy<any>;
82
+ readonly Arrow: PathProxy<any>;
83
+ };
84
+
85
+ export type PathProxy<T> = {
86
+ [K in keyof T]-?: T[K] extends ((...args: any[]) => infer R)
87
+ ? ((...args: any[]) => PathProxy<NonNullable<R>> & PathProxyCore) & PathProxy<NonNullable<R>> & PathProxyCore
88
+ : T[K] extends (object | undefined | null)
89
+ ? PathProxy<NonNullable<T[K]>> & PathProxyCore & ((...args: any[]) => PathProxy<any> & PathProxyCore)
90
+ : PathProxyCore & ((...args: any[]) => PathProxy<any> & PathProxyCore);
91
+ } & PathProxyCore & ((...args: any[]) => PathProxy<any> & PathProxyCore);
45
92
 
46
93
  /**
47
94
  * Options for paths proxy creation.
@@ -58,26 +105,35 @@ export interface PathsOptions {
58
105
  * After the initial #[varName], further property access chains with ?. from the resolved element.
59
106
  * .path returns the #[varName] prefix (optionally with further ?. path).
60
107
  */
61
- function createIdRefProxy(idRef: string, options?: PathsOptions): any {
62
- function handler() {}
63
- return new Proxy(handler, {
64
- get(_, prop: string | symbol) {
65
- if (prop === 'path' || prop === PATH_SYMBOL) {
66
- return idRef;
67
- }
68
- if (typeof prop === 'symbol') return undefined;
69
-
70
- // Chain further path segments after the id ref
71
- const chained = `${idRef}?.${String(prop)}`;
72
- return createIdRefProxy(chained, options);
73
- },
74
- apply(_, __, args) {
75
- if (args.length > 0) {
76
- const arg = args[0];
108
+ function createIdRefProxy(idRef: string, options?: PathsOptions): any {
109
+ function handler() {}
110
+ Object.defineProperty(handler, PATH_SYMBOL, { value: idRef });
111
+ return new Proxy(handler, {
112
+ get(_, prop: string | symbol) {
113
+ if (prop === 'path' || prop === 'Path' || prop === PATH_SYMBOL) {
114
+ return idRef;
115
+ }
116
+ if (typeof prop === 'symbol') return undefined;
117
+
118
+ const reservedToken = getReservedToken(prop);
119
+ if (reservedToken === 'Each') {
120
+ return createIdRefProxy(appendPathSegment(idRef, '@each'), options);
121
+ }
122
+ if (reservedToken && reservedToken !== 'Path' && reservedToken !== 'path') {
123
+ return createIdRefProxy(appendCommandSuffix(idRef, reservedToken), options);
124
+ }
125
+
126
+ // Chain further path segments after the id ref
127
+ const chained = appendPathSegment(idRef, String(prop));
128
+ return createIdRefProxy(chained, options);
129
+ },
130
+ apply(_, __, args) {
131
+ if (args.length > 0) {
132
+ const arg = args[0];
77
133
  let argStr: string;
78
134
  if (arg === true) argStr = 'true';
79
135
  else if (arg === false) argStr = 'false';
80
- else if (arg && typeof arg === 'object' && PATH_SYMBOL in arg) {
136
+ else if (isPathProxy(arg)) {
81
137
  const fullPath = arg[PATH_SYMBOL] as string;
82
138
  argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
83
139
  }
@@ -98,60 +154,72 @@ function createIdRefProxy(idRef: string, options?: PathsOptions): any {
98
154
  * When `aka` is provided, property names that match an alias *value* are output
99
155
  * using the alias *key* instead (reverse alias).
100
156
  */
101
- function createPathProxy(prefix: string, options?: PathsOptions): any {
102
- const aliasMap = options?.aka;
103
-
104
- // Use a function as the target to enable the apply trap
105
- function handler() {}
106
-
107
- return new Proxy(handler, {
108
- get(_, prop: string | symbol) {
109
- if (prop === 'path' || prop === PATH_SYMBOL) {
110
- return prefix.length > 0 ? `?.${prefix}` : '?.';
111
- }
112
- // Ignore symbol access (Symbol.iterator, Symbol.toPrimitive, etc.)
113
- if (typeof prop === 'symbol') return undefined;
114
-
115
- let segment = String(prop);
116
-
117
- // #-prefix: $['#firstName'] '#[firstName]' (cached element ref)
118
- if (segment.startsWith('#')) {
119
- const varName = segment.substring(1);
120
- const idRef = `#[${varName}]`;
157
+ function createPathProxy(prefix: string, options?: PathsOptions): any {
158
+ const aliasMap = options?.aka;
159
+
160
+ // Use a function as the target to enable the apply trap
161
+ function handler() {}
162
+ Object.defineProperty(handler, PATH_SYMBOL, { value: prefix.length > 0 ? `?.${prefix}` : '?.' });
163
+
164
+ return new Proxy(handler, {
165
+ get(_, prop: string | symbol) {
166
+ if (prop === 'path' || prop === 'Path' || prop === PATH_SYMBOL) {
167
+ return serializePath(prefix);
168
+ }
169
+ // Ignore symbol access (Symbol.iterator, Symbol.toPrimitive, etc.)
170
+ if (typeof prop === 'symbol') return undefined;
171
+
172
+ const reservedToken = getReservedToken(String(prop));
173
+ if (reservedToken === 'Path' || reservedToken === 'path') {
174
+ return serializePath(prefix);
175
+ }
176
+ if (reservedToken === 'Each') {
177
+ return createPathProxy(appendPathSegment(prefix, '@each'), options);
178
+ }
179
+ if (reservedToken) {
180
+ return createPathProxy(appendCommandSuffix(prefix, reservedToken), options);
181
+ }
182
+
183
+ let segment = String(prop);
184
+
185
+ // #-prefix: $['#firstName'] → '#[firstName]' (cached element ref)
186
+ if (segment.startsWith('#')) {
187
+ const varName = segment.substring(1);
188
+ const idRef = `#[${varName}]`;
121
189
  // Return a proxy that starts from this id ref (can chain further with ?.)
122
190
  return createIdRefProxy(idRef, options);
123
191
  }
124
192
 
125
193
  // Apply reverse alias: if prop matches an alias value, use the alias key
126
- if (aliasMap) {
127
- for (const [alias, target] of Object.entries(aliasMap)) {
128
- if (target === segment) { segment = alias; break; }
129
- }
130
- }
131
-
132
- const newPath = prefix ? `${prefix}?.${segment}` : segment;
133
- return createPathProxy(newPath, options);
134
- },
135
- apply(_, __, args) {
136
- // Method call syntax: $.querySelector('.username') → extends path with the argument
137
- if (args.length > 0) {
194
+ if (aliasMap) {
195
+ for (const [alias, target] of Object.entries(aliasMap)) {
196
+ if (target === segment) { segment = alias; break; }
197
+ }
198
+ }
199
+
200
+ const newPath = appendPathSegment(prefix, segment);
201
+ return createPathProxy(newPath, options);
202
+ },
203
+ apply(_, __, args) {
204
+ // Method call syntax: $.querySelector('.username') → extends path with the argument
205
+ if (args.length > 0) {
138
206
  const arg = args[0];
139
207
  let argStr: string;
140
208
  if (arg === true) argStr = 'true';
141
209
  else if (arg === false) argStr = 'false';
142
- else if (arg && typeof arg === 'object' && PATH_SYMBOL in arg) {
143
- // Proxy arg — extract path without '?.' prefix
144
- const fullPath = arg[PATH_SYMBOL] as string;
145
- argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
146
- }
147
- else argStr = String(arg);
148
-
149
- const newPath = prefix ? `${prefix}?.${argStr}` : argStr;
150
- return createPathProxy(newPath, options);
151
- }
152
- // No args — method called with no arguments, return self
153
- return createPathProxy(prefix, options);
154
- }
210
+ else if (isPathProxy(arg)) {
211
+ // Proxy arg — extract path without '?.' prefix
212
+ const fullPath = arg[PATH_SYMBOL] as string;
213
+ argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
214
+ }
215
+ else argStr = String(arg);
216
+
217
+ const newPath = appendPathSegment(prefix, argStr);
218
+ return createPathProxy(newPath, options);
219
+ }
220
+ // No args — method called with no arguments, return self
221
+ return createPathProxy(prefix, options);
222
+ }
155
223
  });
156
224
  }
157
225
 
@@ -193,13 +261,13 @@ export function paths<T>(options?: PathsOptions): PathProxy<T> {
193
261
  * count: 1
194
262
  * }
195
263
  */
196
- export function set(lhs: any): { to: (rhs: any) => Record<string, any> } {
197
- const lhsStr = lhs && typeof lhs === 'object' && PATH_SYMBOL in lhs
198
- ? lhs[PATH_SYMBOL]
199
- : String(lhs);
264
+ export function set(lhs: any): { to: (rhs: any) => Record<string, any> } {
265
+ const lhsStr = isPathProxy(lhs)
266
+ ? lhs[PATH_SYMBOL]
267
+ : String(lhs);
200
268
  return {
201
269
  to(rhs: any): Record<string, any> {
202
- const rhsStr = rhs && typeof rhs === 'object' && PATH_SYMBOL in rhs
270
+ const rhsStr = isPathProxy(rhs)
203
271
  ? rhs[PATH_SYMBOL]
204
272
  : rhs;
205
273
  return { [lhsStr]: rhsStr };
@@ -225,9 +293,9 @@ export function set(lhs: any): { to: (rhs: any) => Record<string, any> } {
225
293
  * });
226
294
  * // { assign: { incrementButton: '?.clone?.q?..increment', ... } }
227
295
  */
228
- export function smoothOver(value: any): any {
229
- if (value && typeof value === 'object' && PATH_SYMBOL in value) {
230
- return value[PATH_SYMBOL];
296
+ export function smoothOver(value: any): any {
297
+ if (isPathProxy(value)) {
298
+ return value[PATH_SYMBOL];
231
299
  }
232
300
  if (Array.isArray(value)) {
233
301
  return value.map(smoothOver);
@@ -328,13 +396,13 @@ export function sp(strings: TemplateStringsArray, ...values: any[]): any[] {
328
396
  if (strings[i]) result.push(strings[i]);
329
397
  if (i < values.length) {
330
398
  const v = values[i];
331
- if (v && typeof v === 'object' && PATH_SYMBOL in v) {
399
+ if (isPathProxy(v)) {
332
400
  // Auto-extract path from proxy object
333
401
  result.push(v[PATH_SYMBOL]);
334
402
  } else if (Array.isArray(v)) {
335
403
  // Nested array — recursively extract paths from proxy elements
336
404
  result.push(v.map(el =>
337
- el && typeof el === 'object' && PATH_SYMBOL in el ? el[PATH_SYMBOL] : el
405
+ isPathProxy(el) ? el[PATH_SYMBOL] : el
338
406
  ));
339
407
  } else {
340
408
  result.push(v);
@@ -348,10 +416,12 @@ export function sp(strings: TemplateStringsArray, ...values: any[]): any[] {
348
416
  * Extract the last segment from a `?.`-prefixed path string.
349
417
  * e.g., '?.address?.city' → 'city', '?.firstName' → 'firstName'
350
418
  */
351
- function extractPropName(pathStr: string): string {
352
- const parts = pathStr.split('?.');
353
- return parts[parts.length - 1];
354
- }
419
+ function extractPropName(pathStr: string): string {
420
+ const withoutCommand = pathStr.replace(/(?: \+=| =!| \?=| Y=| -=| =>)$/, '');
421
+ const parts = withoutCommand.split('?.');
422
+ const last = parts[parts.length - 1];
423
+ return last === '@each' ? 'Each' : last;
424
+ }
355
425
 
356
426
  /**
357
427
  * Tagged template literal that produces an array of {prop, val} objects + literal strings.
@@ -384,14 +454,14 @@ export function md(strings: TemplateStringsArray, ...values: any[]): any[] {
384
454
  if (strings[i]) result.push(strings[i]);
385
455
  if (i < values.length) {
386
456
  const v = values[i];
387
- if (v && typeof v === 'object' && PATH_SYMBOL in v) {
457
+ if (isPathProxy(v)) {
388
458
  // Proxy object → {prop, val}
389
459
  const pathStr = v[PATH_SYMBOL] as string;
390
460
  result.push({ prop: extractPropName(pathStr), val: pathStr });
391
461
  } else if (Array.isArray(v)) {
392
462
  // Nested array — recursively convert proxy elements to {prop, val}
393
463
  result.push(v.map(el => {
394
- if (el && typeof el === 'object' && PATH_SYMBOL in el) {
464
+ if (isPathProxy(el)) {
395
465
  const pathStr = el[PATH_SYMBOL] as string;
396
466
  return { prop: extractPropName(pathStr), val: pathStr };
397
467
  }
@@ -400,7 +470,7 @@ export function md(strings: TemplateStringsArray, ...values: any[]): any[] {
400
470
  } else if (v && typeof v === 'object' && 'prop' in v) {
401
471
  // Developer override object — extract val from proxy if present
402
472
  const processed = { ...v };
403
- if (processed.val && typeof processed.val === 'object' && PATH_SYMBOL in processed.val) {
473
+ if (isPathProxy(processed.val)) {
404
474
  processed.val = processed.val[PATH_SYMBOL];
405
475
  }
406
476
  result.push(processed);
package/README.md CHANGED
@@ -4400,9 +4400,10 @@ export default {
4400
4400
  **How `paths` works:**
4401
4401
 
4402
4402
  - `paths<T>()` creates a deeply-proxied object typed as `T`
4403
- - Every property access returns a deeper proxy (e.g., `$.address.city`)
4404
- - `.path` extracts the `?.`-prefixed string: `$.address.city.path` → `'?.address?.city'`
4405
- - Inside `sp` template literals, `.path` is not needed proxy objects are auto-detected
4403
+ - Every property access returns a deeper proxy (e.g., `$.address.city`)
4404
+ - Capitalized reserved tokens like `.Each` and `.EqNot` normalize into command syntax
4405
+ - `.Path` extracts the `?.`-prefixed string: `$.address.city.Path` `'?.address?.city'`
4406
+ - Inside `sp` template literals, `.Path` is not needed — proxy objects are auto-detected
4406
4407
 
4407
4408
  **How `sp` works:**
4408
4409
 
@@ -4421,19 +4422,19 @@ const value = sp`${$.lastName}${[', ', $.middleName]}, ${$.firstName}`;
4421
4422
  // ['?.lastName', [', ', '?.middleName'], ', ', '?.firstName']
4422
4423
  ```
4423
4424
 
4424
- **Using `.path` outside of `sp`:**
4425
+ **Using `.Path` outside of `sp`:**
4425
4426
 
4426
4427
  When you need the path string in a non-`sp` context (object keys, plain arrays, other expressions):
4427
4428
 
4428
4429
  ```TypeScript
4429
4430
  const $ = paths<Person>();
4430
4431
 
4431
- $.lastName.path // '?.lastName'
4432
- $.address.city.path // '?.address?.city'
4432
+ $.lastName.Path // '?.lastName'
4433
+ $.address.city.Path // '?.address?.city'
4433
4434
 
4434
4435
  // As an object key:
4435
4436
  const pattern = {
4436
- [$.textContent.path]: '?.firstName' // '?.textContent': '?.firstName'
4437
+ [$.textContent.Path]: '?.firstName' // '?.textContent': '?.firstName'
4437
4438
  };
4438
4439
  ```
4439
4440
 
@@ -4473,7 +4474,7 @@ md`${$.firstName} ${{ prop: 'birthDate', val: $.birthDT, format: 'long' }}`
4473
4474
  | `sp` | `'?.firstName'` (path string) | `builtIns.join` |
4474
4475
  | `md` | `{ prop: 'firstName', val: '?.firstName' }` | `builtIns.microDataJoin` |
4475
4476
 
4476
- Both auto-detect path proxies (no `.path` needed inside template literals) and preserve nested arrays for optional segments.
4477
+ Both auto-detect path proxies (no `.Path` needed inside template literals) and preserve nested arrays for optional segments.
4477
4478
 
4478
4479
  ## Cached Element Resolution with `#[x]` and `pin`
4479
4480