assign-gingerly 0.0.81 → 0.0.83

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.
@@ -85,6 +85,58 @@ function applyAliases(path: string, aliasMap: Map<string, string>): string {
85
85
  return substituted.join('?.');
86
86
  }
87
87
 
88
+ /**
89
+ * Apply value substitutions to a path string.
90
+ * Replaces complete tokens between `?.` delimiters with their resolved values.
91
+ * Substitutions are applied before aliases.
92
+ */
93
+ function applySubstitutions(path: string, substitutionMap?: Map<string, string>): string {
94
+ if (!substitutionMap || substitutionMap.size === 0) return path;
95
+ const parts = path.split('?.');
96
+ const substituted = parts.map(part => substitutionMap.get(part) ?? part);
97
+ return substituted.join('?.');
98
+ }
99
+
100
+ /**
101
+ * Resolve substitution values declared in options.substitutions.
102
+ * Each substitution path is resolved against the source object without
103
+ * applying substitutions itself, to avoid infinite recursion.
104
+ * Resolved values must be strings and must not contain `?.`.
105
+ */
106
+ function resolveSubstitutions(
107
+ substitutions: Record<string, string> | undefined,
108
+ source: any,
109
+ options: GetValuesOptions | undefined
110
+ ): Map<string, string> {
111
+ const map = new Map<string, string>();
112
+ if (!substitutions) return map;
113
+
114
+ for (const [name, path] of Object.entries(substitutions)) {
115
+ // Resolve the substitution path against the source, but do not apply
116
+ // substitutions to that path. Root references ($0) are also disabled
117
+ // for substitution paths so values are sourced from `from` only.
118
+ const resolved = getValue(
119
+ path,
120
+ source,
121
+ options
122
+ ? { ...options, substitutions: undefined, root: undefined }
123
+ : undefined
124
+ );
125
+ if (typeof resolved !== 'string') {
126
+ throw new Error(
127
+ `Substitution '${name}' must resolve to a string, got ${typeof resolved}`
128
+ );
129
+ }
130
+ if (resolved.includes('?.')) {
131
+ throw new Error(
132
+ `Substitution '${name}' resolved to a string containing '?.', which would alter the path structure: '${resolved}'`
133
+ );
134
+ }
135
+ map.set(name, resolved);
136
+ }
137
+ return map;
138
+ }
139
+
88
140
  /**
89
141
  * Resolve a special root-reference token at the start of a string.
90
142
  * '$0' refers to the first argument passed to assignFrom / resolveValues.
@@ -200,13 +252,15 @@ function getArray(
200
252
  aliasMap: Map<string, string>,
201
253
  withMethods: Set<string> | undefined,
202
254
  protocols: Record<string, (key: string) => any> | undefined,
203
- options?: GetValuesOptions
255
+ options?: GetValuesOptions,
256
+ substitutionMap?: Map<string, string>
204
257
  ): any[] {
205
258
  const permissionProcessor = options?.permissionProcessor;
206
259
  const result: any[] = [];
207
260
  for (const item of arr) {
208
261
  if (typeof item === 'string' && item.startsWith('?.')) {
209
- const aliased = applyAliases(item, aliasMap);
262
+ const substituted = applySubstitutions(item, substitutionMap);
263
+ const aliased = applyAliases(substituted, aliasMap);
210
264
  const parts = parseCachedPath(aliased);
211
265
  result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor));
212
266
  } else if (typeof item === 'string' && item.startsWith('$0')) {
@@ -214,7 +268,8 @@ function getArray(
214
268
  if (rootRef === null) {
215
269
  result.push(source);
216
270
  } else {
217
- const aliased = applyAliases(rootRef.path, aliasMap);
271
+ const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
272
+ const aliased = applyAliases(substitutedPath, aliasMap);
218
273
  const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
219
274
  const parts = parseCachedPath(normalizedPath);
220
275
  result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor));
@@ -222,7 +277,7 @@ function getArray(
222
277
  } else if (typeof item === 'string' && protocols && hasProtocol(item)) {
223
278
  result.push(getProtocolValue(item, protocols, options));
224
279
  } else if (Array.isArray(item)) {
225
- result.push(getArray(item, source, aliasMap, withMethods, protocols, options));
280
+ result.push(getArray(item, source, aliasMap, withMethods, protocols, options, substitutionMap));
226
281
  } else if (item && typeof item === 'object') {
227
282
  const proto = Object.getPrototypeOf(item);
228
283
  if (proto === Object.prototype || proto === null) {
@@ -255,6 +310,7 @@ export function getValues(
255
310
  options?: GetValuesOptions
256
311
  ): Record<string, any> {
257
312
  const { aliasMap, withMethods } = normalizeAliasOptions(options);
313
+ const substitutionMap = resolveSubstitutions(options?.substitutions, source, options);
258
314
 
259
315
  const protocols = options?.protocols;
260
316
  const permissionProcessor = options?.permissionProcessor;
@@ -262,7 +318,8 @@ export function getValues(
262
318
  const result: Record<string, any> = {};
263
319
  for (const [key, value] of Object.entries(pattern)) {
264
320
  if (typeof value === 'string' && value.startsWith('?.')) {
265
- const aliased = applyAliases(value, aliasMap);
321
+ const substituted = applySubstitutions(value, substitutionMap);
322
+ const aliased = applyAliases(substituted, aliasMap);
266
323
  const parts = parseCachedPath(aliased);
267
324
  result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
268
325
  } else if (typeof value === 'string' && value.startsWith('$0')) {
@@ -270,7 +327,8 @@ export function getValues(
270
327
  if (rootRef === null) {
271
328
  result[key] = source;
272
329
  } else {
273
- const aliased = applyAliases(rootRef.path, aliasMap);
330
+ const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
331
+ const aliased = applyAliases(substitutedPath, aliasMap);
274
332
  const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
275
333
  const parts = parseCachedPath(normalizedPath);
276
334
  result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
@@ -278,7 +336,7 @@ export function getValues(
278
336
  } else if (typeof value === 'string' && protocols && hasProtocol(value)) {
279
337
  result[key] = getProtocolValue(value, protocols, options);
280
338
  } else if (Array.isArray(value)) {
281
- result[key] = getArray(value, source, aliasMap, withMethods, protocols, options);
339
+ result[key] = getArray(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
282
340
  } else if (typeof value === 'object' && value !== null) {
283
341
  const proto = Object.getPrototypeOf(value);
284
342
  if (proto === Object.prototype || proto === null) {
@@ -314,10 +372,13 @@ export function getValue(
314
372
  return path;
315
373
  }
316
374
 
317
- let aliased = path;
375
+ const substitutionMap = resolveSubstitutions(options?.substitutions, source, options);
376
+
377
+ const substituted = applySubstitutions(path, substitutionMap);
378
+ let aliased = substituted;
318
379
  const { aliasMap } = normalizeAliasOptions(options);
319
380
  if (aliasMap.size > 0) {
320
- aliased = applyAliases(path, aliasMap);
381
+ aliased = applyAliases(substituted, aliasMap);
321
382
  }
322
383
 
323
384
  const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
@@ -267,6 +267,17 @@ export interface IAssignGingerlyOptions {
267
267
  */
268
268
  aka?: Record<string, string>;
269
269
 
270
+ /**
271
+ * Value substitutions for path segments.
272
+ * Each key names a placeholder; the value is a `?.`-delimited path resolved
273
+ * against the source (`from`) object. The resolved string value replaces any
274
+ * matching whole path segment in RHS path strings before path evaluation.
275
+ *
276
+ * Substitution values must be strings and must not contain the `?.` sequence,
277
+ * otherwise an error is thrown.
278
+ */
279
+ substitutions?: Record<string, string>;
280
+
270
281
  /**
271
282
  * Shorthand for binding method aliases from the source object.
272
283
  * Each entry maps an alias to a method name and is normalized into
@@ -357,6 +368,16 @@ export interface AssignFromOptions {
357
368
  /** Alias mappings for path segments */
358
369
  aka?: Record<string, string>;
359
370
 
371
+ /**
372
+ * Value substitutions for path segments.
373
+ * Each key names a placeholder; the value is a `?.`-delimited path resolved
374
+ * against `from`. The resolved string value replaces any matching whole path
375
+ * segment in RHS path strings before path evaluation.
376
+ *
377
+ * Substitution values must be strings and must not contain `?.`.
378
+ */
379
+ substitutions?: Record<string, string>;
380
+
360
381
  /** AbortSignal for cleanup */
361
382
  signal?: AbortSignal;
362
383