assign-gingerly 0.0.88 → 0.0.90

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
@@ -52,7 +52,12 @@ function appendCommandSuffix(prefix, token) {
52
52
  function getReservedToken(prop) {
53
53
  if (prop === 'Path')
54
54
  return prop;
55
- if (prop in COMMAND_TOKEN_SUFFIXES)
55
+ // Own-property check only — `prop in COMMAND_TOKEN_SUFFIXES` would also match
56
+ // inherited Object.prototype members (`toString`, `valueOf`, `toLocaleString`,
57
+ // `hasOwnProperty`, `constructor`, …), so a path segment named after one of
58
+ // those (e.g. `$.count.toLocaleString`) would be mistaken for a command token
59
+ // and produce a garbled path.
60
+ if (Object.hasOwn(COMMAND_TOKEN_SUFFIXES, prop))
56
61
  return prop;
57
62
  return undefined;
58
63
  }
@@ -193,6 +198,13 @@ function createPathProxy(prefix, options) {
193
198
  * const $ = paths<MyEl>({ aka: { q: 'querySelector' } });
194
199
  * $.querySelector('.user').textContent.Path // '?.q?..user?.textContent'
195
200
  *
201
+ * // To write alias keys (emoji) directly in the chain, pass their union as the
202
+ * // second type argument — TypeScript cannot infer it while `T` is explicit:
203
+ * import { akaMethods as m } from 'assign-gingerly/DX/emojis.js';
204
+ * const $ = paths<MyEl, keyof typeof m>({ aka: m });
205
+ * $.count['🌐'].Path // '?.count?.🌐'
206
+ * // (or just use the real method name: $.count.toLocaleString — reverse-aliased to 🌐)
207
+ *
196
208
  * // Inside sp template literals, .Path is not needed:
197
209
  * sp`${$.lastName}, ${$.firstName}` // ['?.lastName', ', ', '?.firstName']
198
210
  */
package/DX/paths.ts CHANGED
@@ -60,7 +60,12 @@ function appendCommandSuffix(prefix: string, token: CommandToken): string {
60
60
 
61
61
  function getReservedToken(prop: string): CommandToken | 'Path' | undefined {
62
62
  if (prop === 'Path') return prop;
63
- if (prop in COMMAND_TOKEN_SUFFIXES) return prop as CommandToken;
63
+ // Own-property check only — `prop in COMMAND_TOKEN_SUFFIXES` would also match
64
+ // inherited Object.prototype members (`toString`, `valueOf`, `toLocaleString`,
65
+ // `hasOwnProperty`, `constructor`, …), so a path segment named after one of
66
+ // those (e.g. `$.count.toLocaleString`) would be mistaken for a command token
67
+ // and produce a garbled path.
68
+ if (Object.hasOwn(COMMAND_TOKEN_SUFFIXES, prop)) return prop as CommandToken;
64
69
  return undefined;
65
70
  }
66
71
 
@@ -70,26 +75,36 @@ function getReservedToken(prop: string): CommandToken | 'Path' | undefined {
70
75
  * with a serialized path string accessor — while providing full autocomplete.
71
76
  * Enhanced: also callable (for method call syntax) and includes reserved
72
77
  * command markers such as `Each`, `EqNot`, and `PlusEq`.
78
+ *
79
+ * `Extra` carries alias keys (`aka`) and method names (`withMethods`) passed to
80
+ * `paths(...)` so they are accepted at every level of the chain — e.g.
81
+ * `$.count['🌐']` when `paths({ aka: { '🌐': 'toLocaleString' } })`.
73
82
  */
74
- export type PathProxyCore = {
83
+ export type PathProxyCore<Extra extends string = never> = {
75
84
  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
- readonly EqAmp: PathProxy<any>;
85
+ readonly Each: PathProxy<any, Extra>;
86
+ readonly EqNot: PathProxy<any, Extra>;
87
+ readonly PlusEq: PathProxy<any, Extra>;
88
+ readonly QMEq: PathProxy<any, Extra>;
89
+ readonly YEq: PathProxy<any, Extra>;
90
+ readonly MinusEq: PathProxy<any, Extra>;
91
+ readonly Arrow: PathProxy<any, Extra>;
92
+ readonly EqAmp: PathProxy<any, Extra>;
93
+ } & {
94
+ readonly [K in Extra]: PathProxy<any, Extra> & PathProxyCore<Extra>
95
+ & ((...args: any[]) => PathProxy<any, Extra> & PathProxyCore<Extra>);
84
96
  };
85
97
 
86
- export type PathProxy<T> = {
98
+ type PathLeaf<Extra extends string = never> =
99
+ PathProxyCore<Extra> & ((...args: any[]) => PathProxy<any, Extra> & PathProxyCore<Extra>);
100
+
101
+ export type PathProxy<T, Extra extends string = never> = {
87
102
  [K in keyof T]-?: T[K] extends ((...args: any[]) => infer R)
88
- ? ((...args: any[]) => PathProxy<NonNullable<R>> & PathProxyCore) & PathProxy<NonNullable<R>> & PathProxyCore
103
+ ? ((...args: any[]) => PathProxy<NonNullable<R>, Extra> & PathProxyCore<Extra>) & PathProxy<NonNullable<R>, Extra> & PathProxyCore<Extra>
89
104
  : T[K] extends (object | undefined | null)
90
- ? PathProxy<NonNullable<T[K]>> & PathProxyCore & ((...args: any[]) => PathProxy<any> & PathProxyCore)
91
- : PathProxyCore & ((...args: any[]) => PathProxy<any> & PathProxyCore);
92
- } & PathProxyCore & ((...args: any[]) => PathProxy<any> & PathProxyCore);
105
+ ? PathProxy<NonNullable<T[K]>, Extra> & PathProxyCore<Extra> & ((...args: any[]) => PathProxy<any, Extra> & PathProxyCore<Extra>)
106
+ : PathLeaf<Extra>;
107
+ } & PathLeaf<Extra>;
93
108
 
94
109
  /**
95
110
  * Options for paths proxy creation.
@@ -239,11 +254,18 @@ function createPathProxy(prefix: string, options?: PathsOptions): any {
239
254
  * // With aka (reverse alias applied):
240
255
  * const $ = paths<MyEl>({ aka: { q: 'querySelector' } });
241
256
  * $.querySelector('.user').textContent.Path // '?.q?..user?.textContent'
242
- *
257
+ *
258
+ * // To write alias keys (emoji) directly in the chain, pass their union as the
259
+ * // second type argument — TypeScript cannot infer it while `T` is explicit:
260
+ * import { akaMethods as m } from 'assign-gingerly/DX/emojis.js';
261
+ * const $ = paths<MyEl, keyof typeof m>({ aka: m });
262
+ * $.count['🌐'].Path // '?.count?.🌐'
263
+ * // (or just use the real method name: $.count.toLocaleString — reverse-aliased to 🌐)
264
+ *
243
265
  * // Inside sp template literals, .Path is not needed:
244
266
  * sp`${$.lastName}, ${$.firstName}` // ['?.lastName', ', ', '?.firstName']
245
267
  */
246
- export function paths<T>(options?: PathsOptions): PathProxy<T> {
268
+ export function paths<T, Extra extends string = never>(options?: PathsOptions): PathProxy<T, Extra> {
247
269
  return createPathProxy('', options) as any;
248
270
  }
249
271
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.88",
3
+ "version": "0.0.90",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {
@@ -145,12 +145,23 @@ function navigatePath(source, parts, withMethods, permissionProcessor) {
145
145
  while (i < parts.length) {
146
146
  if (current == null)
147
147
  return current;
148
- const part = parts[i];
149
- if (isAllowedMethod(part, withMethods, permissionProcessor)) {
148
+ const rawPart = parts[i];
149
+ // A trailing | forces a zero-argument method call: 'toLocaleString|' calls
150
+ // toLocaleString() without consuming the next segment. Only applies to
151
+ // names listed in withMethods; otherwise | is part of a literal key.
152
+ const isZeroArg = rawPart.endsWith('|')
153
+ && isAllowedMethod(rawPart.slice(0, -1), withMethods, permissionProcessor);
154
+ const part = isZeroArg ? rawPart.slice(0, -1) : rawPart;
155
+ if (isZeroArg || isAllowedMethod(part, withMethods, permissionProcessor)) {
150
156
  const method = current[part];
151
157
  if (typeof method === 'function') {
152
158
  const nextPart = parts[i + 1];
153
- if (nextPart !== undefined && !isAllowedMethod(nextPart, withMethods, permissionProcessor)) {
159
+ // Consecutive methods (including a |-marked next segment) mean a
160
+ // zero-arg call; otherwise the next segment is the string argument.
161
+ const nextIsMethod = nextPart !== undefined
162
+ && (isAllowedMethod(nextPart, withMethods, permissionProcessor)
163
+ || (nextPart.endsWith('|') && isAllowedMethod(nextPart.slice(0, -1), withMethods, permissionProcessor)));
164
+ if (!isZeroArg && nextPart !== undefined && !nextIsMethod) {
154
165
  current = method.call(current, nextPart);
155
166
  i += 2;
156
167
  }
@@ -165,7 +176,7 @@ function navigatePath(source, parts, withMethods, permissionProcessor) {
165
176
  }
166
177
  }
167
178
  else {
168
- current = current[part];
179
+ current = current[rawPart];
169
180
  i++;
170
181
  }
171
182
  }
@@ -182,13 +182,25 @@ function navigatePath(
182
182
  while (i < parts.length) {
183
183
  if (current == null) return current;
184
184
 
185
- const part = parts[i];
185
+ const rawPart = parts[i];
186
186
 
187
- if (isAllowedMethod(part, withMethods, permissionProcessor)) {
187
+ // A trailing | forces a zero-argument method call: 'toLocaleString|' calls
188
+ // toLocaleString() without consuming the next segment. Only applies to
189
+ // names listed in withMethods; otherwise | is part of a literal key.
190
+ const isZeroArg = rawPart.endsWith('|')
191
+ && isAllowedMethod(rawPart.slice(0, -1), withMethods, permissionProcessor);
192
+ const part = isZeroArg ? rawPart.slice(0, -1) : rawPart;
193
+
194
+ if (isZeroArg || isAllowedMethod(part, withMethods, permissionProcessor)) {
188
195
  const method = current[part];
189
196
  if (typeof method === 'function') {
190
197
  const nextPart = parts[i + 1];
191
- if (nextPart !== undefined && !isAllowedMethod(nextPart, withMethods, permissionProcessor)) {
198
+ // Consecutive methods (including a |-marked next segment) mean a
199
+ // zero-arg call; otherwise the next segment is the string argument.
200
+ const nextIsMethod = nextPart !== undefined
201
+ && (isAllowedMethod(nextPart, withMethods, permissionProcessor)
202
+ || (nextPart.endsWith('|') && isAllowedMethod(nextPart.slice(0, -1), withMethods, permissionProcessor)));
203
+ if (!isZeroArg && nextPart !== undefined && !nextIsMethod) {
192
204
  current = method.call(current, nextPart);
193
205
  i += 2;
194
206
  } else {
@@ -200,7 +212,7 @@ function navigatePath(
200
212
  i++;
201
213
  }
202
214
  } else {
203
- current = current[part];
215
+ current = current[rawPart];
204
216
  i++;
205
217
  }
206
218
  }