assign-gingerly 0.0.90 → 0.0.92

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.
@@ -188,6 +188,80 @@ function navigatePath(source, parts, withMethods, permissionProcessor) {
188
188
  function hasProtocol(value) {
189
189
  return value.includes('://');
190
190
  }
191
+ /**
192
+ * Detect a leading run of `!` characters used as a boolean coercion / negation
193
+ * marker (`!` = negate, `!!` = coerce to boolean, `!!!` = negate, …). Returns the
194
+ * count and the remaining string, or null when the value does not start with `!`.
195
+ *
196
+ * The marker is only *honored* when the remainder is itself a resolvable
197
+ * reference (see `looksLikeReference`); otherwise the original string is a plain
198
+ * literal — e.g. a CSS `!important` passes through untouched.
199
+ */
200
+ function parseNegationMarker(value) {
201
+ let count = 0;
202
+ while (count < value.length && value.charCodeAt(count) === 33 /* '!' */)
203
+ count++;
204
+ if (count === 0)
205
+ return null;
206
+ return { count, rest: value.slice(count) };
207
+ }
208
+ /**
209
+ * Whether a string should be resolved as a value reference rather than kept as a
210
+ * literal: a `?.` path, a `$0` root reference, or a recognized protocol.
211
+ */
212
+ function looksLikeReference(value, protocols) {
213
+ return value.startsWith('?.')
214
+ || value.startsWith('$0')
215
+ || (!!protocols && hasProtocol(value));
216
+ }
217
+ /**
218
+ * Resolve a single reference string (`?.` path, `$0` root ref, or protocol) to
219
+ * its value. Callers must confirm `looksLikeReference(value)` first.
220
+ */
221
+ function resolveReferenceString(value, source, aliasMap, withMethods, protocols, options, substitutionMap) {
222
+ const permissionProcessor = options?.permissionProcessor;
223
+ if (value.startsWith('?.')) {
224
+ const substituted = applySubstitutions(value, substitutionMap);
225
+ const aliased = applyAliases(substituted, aliasMap);
226
+ const parts = parseCachedPath(aliased);
227
+ return parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
228
+ }
229
+ if (value.startsWith('$0')) {
230
+ const rootRef = resolveRootReference(value, source, options?.root);
231
+ if (rootRef === null)
232
+ return source;
233
+ const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
234
+ const aliased = applyAliases(substitutedPath, aliasMap);
235
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
236
+ const parts = parseCachedPath(normalizedPath);
237
+ return parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
238
+ }
239
+ // protocol — looksLikeReference already ensured protocols is defined
240
+ return getProtocolValue(value, protocols, options);
241
+ }
242
+ /**
243
+ * Resolve a string RHS value: a reference (`?.` / `$0` / protocol), a
244
+ * `!`-prefixed coercion/negation of a reference, or a plain literal.
245
+ */
246
+ function resolveStringValue(value, source, aliasMap, withMethods, protocols, options, substitutionMap) {
247
+ // A leading `!` run can only be a coercion/negation marker or a literal —
248
+ // no `?.` path, `$0` ref, or protocol name starts with `!`. Check it first so
249
+ // `hasProtocol` inside `looksLikeReference` can't misread `!!proto://x`.
250
+ if (value.charCodeAt(0) === 33 /* '!' */) {
251
+ const neg = parseNegationMarker(value);
252
+ if (neg !== null && looksLikeReference(neg.rest, protocols)) {
253
+ let resolved = resolveReferenceString(neg.rest, source, aliasMap, withMethods, protocols, options, substitutionMap);
254
+ for (let i = 0; i < neg.count; i++)
255
+ resolved = !resolved;
256
+ return resolved;
257
+ }
258
+ return value;
259
+ }
260
+ if (looksLikeReference(value, protocols)) {
261
+ return resolveReferenceString(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
262
+ }
263
+ return value;
264
+ }
191
265
  /**
192
266
  * Resolve a protocol-prefixed value synchronously.
193
267
  */
@@ -212,30 +286,10 @@ function getProtocolValue(value, protocols, options) {
212
286
  * Recurses into nested arrays and plain objects.
213
287
  */
214
288
  function getArray(arr, source, aliasMap, withMethods, protocols, options, substitutionMap) {
215
- const permissionProcessor = options?.permissionProcessor;
216
289
  const result = [];
217
290
  for (const item of arr) {
218
- if (typeof item === 'string' && item.startsWith('?.')) {
219
- const substituted = applySubstitutions(item, substitutionMap);
220
- const aliased = applyAliases(substituted, aliasMap);
221
- const parts = parseCachedPath(aliased);
222
- result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor));
223
- }
224
- else if (typeof item === 'string' && item.startsWith('$0')) {
225
- const rootRef = resolveRootReference(item, source, options?.root);
226
- if (rootRef === null) {
227
- result.push(source);
228
- }
229
- else {
230
- const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
231
- const aliased = applyAliases(substitutedPath, aliasMap);
232
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
233
- const parts = parseCachedPath(normalizedPath);
234
- result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor));
235
- }
236
- }
237
- else if (typeof item === 'string' && protocols && hasProtocol(item)) {
238
- result.push(getProtocolValue(item, protocols, options));
291
+ if (typeof item === 'string') {
292
+ result.push(resolveStringValue(item, source, aliasMap, withMethods, protocols, options, substitutionMap));
239
293
  }
240
294
  else if (Array.isArray(item)) {
241
295
  result.push(getArray(item, source, aliasMap, withMethods, protocols, options, substitutionMap));
@@ -271,30 +325,10 @@ export function getValues(pattern, source, options) {
271
325
  const { aliasMap, withMethods } = normalizeAliasOptions(options);
272
326
  const substitutionMap = resolveSubstitutions(options?.substitutions, source, options);
273
327
  const protocols = options?.protocols;
274
- const permissionProcessor = options?.permissionProcessor;
275
328
  const result = {};
276
329
  for (const [key, value] of Object.entries(pattern)) {
277
- if (typeof value === 'string' && value.startsWith('?.')) {
278
- const substituted = applySubstitutions(value, substitutionMap);
279
- const aliased = applyAliases(substituted, aliasMap);
280
- const parts = parseCachedPath(aliased);
281
- result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
282
- }
283
- else if (typeof value === 'string' && value.startsWith('$0')) {
284
- const rootRef = resolveRootReference(value, source, options?.root);
285
- if (rootRef === null) {
286
- result[key] = source;
287
- }
288
- else {
289
- const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
290
- const aliased = applyAliases(substitutedPath, aliasMap);
291
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
292
- const parts = parseCachedPath(normalizedPath);
293
- result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
294
- }
295
- }
296
- else if (typeof value === 'string' && protocols && hasProtocol(value)) {
297
- result[key] = getProtocolValue(value, protocols, options);
330
+ if (typeof value === 'string') {
331
+ result[key] = resolveStringValue(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
298
332
  }
299
333
  else if (Array.isArray(value)) {
300
334
  result[key] = getArray(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
@@ -323,6 +357,15 @@ export function getValues(pattern, source, options) {
323
357
  * @returns The resolved value, or undefined if any segment is nullish
324
358
  */
325
359
  export function getValue(path, source, options) {
360
+ // Leading `!` run: coerce/negate the resolved reference (`!` negate, `!!` boolean, …).
361
+ // Only honored in front of a `?.` path or `$0` root reference — otherwise literal.
362
+ const neg = parseNegationMarker(path);
363
+ if (neg !== null && (neg.rest.startsWith('?.') || neg.rest.startsWith('$0'))) {
364
+ let resolved = getValue(neg.rest, source, options);
365
+ for (let i = 0; i < neg.count; i++)
366
+ resolved = !resolved;
367
+ return resolved;
368
+ }
326
369
  const rootRef = resolveRootReference(path, source, options?.root);
327
370
  if (rootRef) {
328
371
  path = rootRef.path;
@@ -227,6 +227,99 @@ function hasProtocol(value: string): boolean {
227
227
  return value.includes('://');
228
228
  }
229
229
 
230
+ /**
231
+ * Detect a leading run of `!` characters used as a boolean coercion / negation
232
+ * marker (`!` = negate, `!!` = coerce to boolean, `!!!` = negate, …). Returns the
233
+ * count and the remaining string, or null when the value does not start with `!`.
234
+ *
235
+ * The marker is only *honored* when the remainder is itself a resolvable
236
+ * reference (see `looksLikeReference`); otherwise the original string is a plain
237
+ * literal — e.g. a CSS `!important` passes through untouched.
238
+ */
239
+ function parseNegationMarker(value: string): { count: number; rest: string } | null {
240
+ let count = 0;
241
+ while (count < value.length && value.charCodeAt(count) === 33 /* '!' */) count++;
242
+ if (count === 0) return null;
243
+ return { count, rest: value.slice(count) };
244
+ }
245
+
246
+ /**
247
+ * Whether a string should be resolved as a value reference rather than kept as a
248
+ * literal: a `?.` path, a `$0` root reference, or a recognized protocol.
249
+ */
250
+ function looksLikeReference(
251
+ value: string,
252
+ protocols: Record<string, (key: string) => any> | undefined
253
+ ): boolean {
254
+ return value.startsWith('?.')
255
+ || value.startsWith('$0')
256
+ || (!!protocols && hasProtocol(value));
257
+ }
258
+
259
+ /**
260
+ * Resolve a single reference string (`?.` path, `$0` root ref, or protocol) to
261
+ * its value. Callers must confirm `looksLikeReference(value)` first.
262
+ */
263
+ function resolveReferenceString(
264
+ value: string,
265
+ source: any,
266
+ aliasMap: Map<string, string>,
267
+ withMethods: Set<string> | undefined,
268
+ protocols: Record<string, (key: string) => any> | undefined,
269
+ options: GetValuesOptions | undefined,
270
+ substitutionMap: Map<string, string> | undefined
271
+ ): any {
272
+ const permissionProcessor = options?.permissionProcessor;
273
+ if (value.startsWith('?.')) {
274
+ const substituted = applySubstitutions(value, substitutionMap);
275
+ const aliased = applyAliases(substituted, aliasMap);
276
+ const parts = parseCachedPath(aliased);
277
+ return parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
278
+ }
279
+ if (value.startsWith('$0')) {
280
+ const rootRef = resolveRootReference(value, source, options?.root);
281
+ if (rootRef === null) return source;
282
+ const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
283
+ const aliased = applyAliases(substitutedPath, aliasMap);
284
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
285
+ const parts = parseCachedPath(normalizedPath);
286
+ return parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
287
+ }
288
+ // protocol — looksLikeReference already ensured protocols is defined
289
+ return getProtocolValue(value, protocols!, options);
290
+ }
291
+
292
+ /**
293
+ * Resolve a string RHS value: a reference (`?.` / `$0` / protocol), a
294
+ * `!`-prefixed coercion/negation of a reference, or a plain literal.
295
+ */
296
+ function resolveStringValue(
297
+ value: string,
298
+ source: any,
299
+ aliasMap: Map<string, string>,
300
+ withMethods: Set<string> | undefined,
301
+ protocols: Record<string, (key: string) => any> | undefined,
302
+ options: GetValuesOptions | undefined,
303
+ substitutionMap: Map<string, string> | undefined
304
+ ): any {
305
+ // A leading `!` run can only be a coercion/negation marker or a literal —
306
+ // no `?.` path, `$0` ref, or protocol name starts with `!`. Check it first so
307
+ // `hasProtocol` inside `looksLikeReference` can't misread `!!proto://x`.
308
+ if (value.charCodeAt(0) === 33 /* '!' */) {
309
+ const neg = parseNegationMarker(value);
310
+ if (neg !== null && looksLikeReference(neg.rest, protocols)) {
311
+ let resolved = resolveReferenceString(neg.rest, source, aliasMap, withMethods, protocols, options, substitutionMap);
312
+ for (let i = 0; i < neg.count; i++) resolved = !resolved;
313
+ return resolved;
314
+ }
315
+ return value;
316
+ }
317
+ if (looksLikeReference(value, protocols)) {
318
+ return resolveReferenceString(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
319
+ }
320
+ return value;
321
+ }
322
+
230
323
  /**
231
324
  * Resolve a protocol-prefixed value synchronously.
232
325
  */
@@ -268,27 +361,10 @@ function getArray(
268
361
  options?: GetValuesOptions,
269
362
  substitutionMap?: Map<string, string>
270
363
  ): any[] {
271
- const permissionProcessor = options?.permissionProcessor;
272
364
  const result: any[] = [];
273
365
  for (const item of arr) {
274
- if (typeof item === 'string' && item.startsWith('?.')) {
275
- const substituted = applySubstitutions(item, substitutionMap);
276
- const aliased = applyAliases(substituted, aliasMap);
277
- const parts = parseCachedPath(aliased);
278
- result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor));
279
- } else if (typeof item === 'string' && item.startsWith('$0')) {
280
- const rootRef = resolveRootReference(item, source, options?.root);
281
- if (rootRef === null) {
282
- result.push(source);
283
- } else {
284
- const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
285
- const aliased = applyAliases(substitutedPath, aliasMap);
286
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
287
- const parts = parseCachedPath(normalizedPath);
288
- result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor));
289
- }
290
- } else if (typeof item === 'string' && protocols && hasProtocol(item)) {
291
- result.push(getProtocolValue(item, protocols, options));
366
+ if (typeof item === 'string') {
367
+ result.push(resolveStringValue(item, source, aliasMap, withMethods, protocols, options, substitutionMap));
292
368
  } else if (Array.isArray(item)) {
293
369
  result.push(getArray(item, source, aliasMap, withMethods, protocols, options, substitutionMap));
294
370
  } else if (item && typeof item === 'object') {
@@ -326,28 +402,11 @@ export function getValues(
326
402
  const substitutionMap = resolveSubstitutions(options?.substitutions, source, options);
327
403
 
328
404
  const protocols = options?.protocols;
329
- const permissionProcessor = options?.permissionProcessor;
330
405
 
331
406
  const result: Record<string, any> = {};
332
407
  for (const [key, value] of Object.entries(pattern)) {
333
- if (typeof value === 'string' && value.startsWith('?.')) {
334
- const substituted = applySubstitutions(value, substitutionMap);
335
- const aliased = applyAliases(substituted, aliasMap);
336
- const parts = parseCachedPath(aliased);
337
- result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
338
- } else if (typeof value === 'string' && value.startsWith('$0')) {
339
- const rootRef = resolveRootReference(value, source, options?.root);
340
- if (rootRef === null) {
341
- result[key] = source;
342
- } else {
343
- const substitutedPath = applySubstitutions(rootRef.path, substitutionMap);
344
- const aliased = applyAliases(substitutedPath, aliasMap);
345
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
346
- const parts = parseCachedPath(normalizedPath);
347
- result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
348
- }
349
- } else if (typeof value === 'string' && protocols && hasProtocol(value)) {
350
- result[key] = getProtocolValue(value, protocols, options);
408
+ if (typeof value === 'string') {
409
+ result[key] = resolveStringValue(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
351
410
  } else if (Array.isArray(value)) {
352
411
  result[key] = getArray(value, source, aliasMap, withMethods, protocols, options, substitutionMap);
353
412
  } else if (typeof value === 'object' && value !== null) {
@@ -377,6 +436,15 @@ export function getValue(
377
436
  source: any,
378
437
  options?: GetValuesOptions
379
438
  ): any {
439
+ // Leading `!` run: coerce/negate the resolved reference (`!` negate, `!!` boolean, …).
440
+ // Only honored in front of a `?.` path or `$0` root reference — otherwise literal.
441
+ const neg = parseNegationMarker(path);
442
+ if (neg !== null && (neg.rest.startsWith('?.') || neg.rest.startsWith('$0'))) {
443
+ let resolved = getValue(neg.rest, source, options);
444
+ for (let i = 0; i < neg.count; i++) resolved = !resolved;
445
+ return resolved;
446
+ }
447
+
380
448
  const rootRef = resolveRootReference(path, source, options?.root);
381
449
  if (rootRef) {
382
450
  path = rootRef.path;
@@ -360,6 +360,14 @@ export interface IAssignGingerlyOptions {
360
360
  * ]
361
361
  */
362
362
  enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
363
+
364
+ /**
365
+ * Handler implementations scoped to this call, forwarded to `assignFrom` when
366
+ * these options are reused by higher-level features (e.g. roundabout merges).
367
+ * Key: the `do` name referenced in handler configs. Value: a class
368
+ * constructor, an import path, or a `builtIns.*` alias string.
369
+ */
370
+ handlers?: Record<string, AssignFromHandlerConstructor | string>;
363
371
  }
364
372
 
365
373
  /**