assign-gingerly 0.0.31 → 0.0.33
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/README.md +416 -140
- package/assignFrom.js +27 -0
- package/assignFrom.ts +37 -0
- package/assignGingerly.js +244 -25
- package/assignGingerly.ts +310 -25
- package/eachTime.js +110 -0
- package/eachTime.ts +136 -0
- package/index.js +2 -0
- package/index.ts +2 -0
- package/object-extension.js +65 -12
- package/object-extension.ts +74 -15
- package/package.json +9 -1
- package/resolveValues.js +44 -0
- package/resolveValues.ts +45 -0
- package/types/assign-gingerly/types.d.ts +11 -2
package/assignFrom.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings against a source object, then assign the
|
|
3
|
+
* resolved values into a target using assignGingerly.
|
|
4
|
+
*
|
|
5
|
+
* Combines resolveValues + assignGingerly into a single call.
|
|
6
|
+
* Inherits all assignGingerly options (withMethods, aka, signal, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @param target - Object to merge resolved values into
|
|
9
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
10
|
+
* @param options - Options including `from` (source object) and any assignGingerly options
|
|
11
|
+
* @returns The target object after merging
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const source = { theme: { color: 'red' }, label: 'Hello' };
|
|
15
|
+
* const target = { color: 'blue', text: '' };
|
|
16
|
+
* assignFrom(target, {
|
|
17
|
+
* color: '?.theme?.color',
|
|
18
|
+
* text: '?.label'
|
|
19
|
+
* }, { from: source });
|
|
20
|
+
* // target is now { color: 'red', text: 'Hello' }
|
|
21
|
+
*/
|
|
22
|
+
import { resolveValues } from './resolveValues.js';
|
|
23
|
+
import assignGingerly from './assignGingerly.js';
|
|
24
|
+
export function assignFrom(target, pattern, options) {
|
|
25
|
+
const resolved = resolveValues(pattern, options.from);
|
|
26
|
+
return assignGingerly(target, resolved, options);
|
|
27
|
+
}
|
package/assignFrom.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings against a source object, then assign the
|
|
3
|
+
* resolved values into a target using assignGingerly.
|
|
4
|
+
*
|
|
5
|
+
* Combines resolveValues + assignGingerly into a single call.
|
|
6
|
+
* Inherits all assignGingerly options (withMethods, aka, signal, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @param target - Object to merge resolved values into
|
|
9
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
10
|
+
* @param options - Options including `from` (source object) and any assignGingerly options
|
|
11
|
+
* @returns The target object after merging
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const source = { theme: { color: 'red' }, label: 'Hello' };
|
|
15
|
+
* const target = { color: 'blue', text: '' };
|
|
16
|
+
* assignFrom(target, {
|
|
17
|
+
* color: '?.theme?.color',
|
|
18
|
+
* text: '?.label'
|
|
19
|
+
* }, { from: source });
|
|
20
|
+
* // target is now { color: 'red', text: 'Hello' }
|
|
21
|
+
*/
|
|
22
|
+
import { resolveValues } from './resolveValues.js';
|
|
23
|
+
import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
|
|
24
|
+
|
|
25
|
+
export interface AssignFromOptions extends IAssignGingerlyOptions {
|
|
26
|
+
/** Source object to resolve RHS path strings against */
|
|
27
|
+
from: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function assignFrom(
|
|
31
|
+
target: any,
|
|
32
|
+
pattern: Record<string, any>,
|
|
33
|
+
options: AssignFromOptions
|
|
34
|
+
): any {
|
|
35
|
+
const resolved = resolveValues(pattern, options.from);
|
|
36
|
+
return assignGingerly(target, resolved, options);
|
|
37
|
+
}
|
package/assignGingerly.js
CHANGED
|
@@ -228,12 +228,17 @@ function ensureNestedPath(obj, pathParts) {
|
|
|
228
228
|
return current;
|
|
229
229
|
}
|
|
230
230
|
/**
|
|
231
|
-
* Helper function to check if a property
|
|
232
|
-
* A property is
|
|
231
|
+
* Helper function to check if a property should be merged into rather than replaced.
|
|
232
|
+
* A property is non-replaceable if:
|
|
233
233
|
* - It's a data property with writable: false, OR
|
|
234
234
|
* - It's an accessor property with a getter but no setter
|
|
235
|
+
*
|
|
236
|
+
* Properties with both a getter and setter (e.g., element.style) are treated as
|
|
237
|
+
* replaceable — the setter runs with whatever value is provided (garbage in, garbage out).
|
|
238
|
+
*
|
|
239
|
+
* Exported for use by eachTime.ts
|
|
235
240
|
*/
|
|
236
|
-
function isReadonlyProperty(obj, propName) {
|
|
241
|
+
export function isReadonlyProperty(obj, propName) {
|
|
237
242
|
let descriptor = Object.getOwnPropertyDescriptor(obj, propName);
|
|
238
243
|
if (!descriptor) {
|
|
239
244
|
// Check prototype chain
|
|
@@ -251,8 +256,8 @@ function isReadonlyProperty(obj, propName) {
|
|
|
251
256
|
if ('value' in descriptor) {
|
|
252
257
|
return descriptor.writable === false;
|
|
253
258
|
}
|
|
254
|
-
// If it's an accessor property
|
|
255
|
-
if ('get' in descriptor) {
|
|
259
|
+
// If it's an accessor property with a getter but no setter, it's readonly
|
|
260
|
+
if ('get' in descriptor && descriptor.get !== undefined) {
|
|
256
261
|
return descriptor.set === undefined;
|
|
257
262
|
}
|
|
258
263
|
return false;
|
|
@@ -260,8 +265,10 @@ function isReadonlyProperty(obj, propName) {
|
|
|
260
265
|
/**
|
|
261
266
|
* Helper function to check if a value is a class instance (not a plain object)
|
|
262
267
|
* Returns true for instances of classes, false for plain objects, arrays, and primitives
|
|
268
|
+
*
|
|
269
|
+
* Exported for use by eachTime.ts
|
|
263
270
|
*/
|
|
264
|
-
function isClassInstance(value) {
|
|
271
|
+
export function isClassInstance(value) {
|
|
265
272
|
if (!value || typeof value !== 'object')
|
|
266
273
|
return false;
|
|
267
274
|
if (Array.isArray(value))
|
|
@@ -273,8 +280,10 @@ function isClassInstance(value) {
|
|
|
273
280
|
/**
|
|
274
281
|
* Helper function to evaluate a nested path with method calls
|
|
275
282
|
* Handles chained method calls where path segments can be methods
|
|
283
|
+
*
|
|
284
|
+
* Exported for use by eachTime.ts
|
|
276
285
|
*/
|
|
277
|
-
function evaluatePathWithMethods(target, pathParts, value, withMethods) {
|
|
286
|
+
export function evaluatePathWithMethods(target, pathParts, value, withMethods) {
|
|
278
287
|
let current = target;
|
|
279
288
|
let i = 0;
|
|
280
289
|
// Process all segments except the last one
|
|
@@ -319,6 +328,159 @@ function evaluatePathWithMethods(target, pathParts, value, withMethods) {
|
|
|
319
328
|
isMethod: withMethods.has(lastKey)
|
|
320
329
|
};
|
|
321
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Check if a value is iterable (can be used with for...of or has forEach)
|
|
333
|
+
*/
|
|
334
|
+
function isIterable(value) {
|
|
335
|
+
if (value == null)
|
|
336
|
+
return false;
|
|
337
|
+
// Check for Symbol.iterator
|
|
338
|
+
if (typeof value[Symbol.iterator] === 'function')
|
|
339
|
+
return true;
|
|
340
|
+
// Check if it's an Array
|
|
341
|
+
if (Array.isArray(value))
|
|
342
|
+
return true;
|
|
343
|
+
// Check if it's array-like (has length and numeric indices)
|
|
344
|
+
// This covers NodeList, HTMLCollection, etc.
|
|
345
|
+
if (typeof value.length === 'number' && value.length >= 0) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Check if a segment is the forEach symbol (@each) or aliased to it
|
|
352
|
+
*/
|
|
353
|
+
function isForEachSymbol(segment, aliasMap) {
|
|
354
|
+
// Direct match
|
|
355
|
+
if (segment === '@each')
|
|
356
|
+
return true;
|
|
357
|
+
// Check if this segment is aliased to '@each'
|
|
358
|
+
const aliasTarget = aliasMap.get(segment);
|
|
359
|
+
return aliasTarget === '@each';
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Check if a segment is the reactive forEach symbol (@eachTime) or aliased to it
|
|
363
|
+
*/
|
|
364
|
+
function isReactiveForEachSymbol(segment, aliasMap) {
|
|
365
|
+
// Direct match
|
|
366
|
+
if (segment === '@eachTime')
|
|
367
|
+
return true;
|
|
368
|
+
// Check if this segment is aliased to '@eachTime'
|
|
369
|
+
const aliasTarget = aliasMap.get(segment);
|
|
370
|
+
return aliasTarget === '@eachTime';
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Apply a path to each item in an iterable
|
|
374
|
+
*/
|
|
375
|
+
function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, options) {
|
|
376
|
+
// Convert to array for iteration
|
|
377
|
+
const items = Array.isArray(iterable) ? iterable : Array.from(iterable);
|
|
378
|
+
// Apply the remaining path to each item
|
|
379
|
+
for (const item of items) {
|
|
380
|
+
if (remainingPath.length === 0) {
|
|
381
|
+
// No remaining path, can't do anything
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
// Check if there's another @each in the remaining path
|
|
385
|
+
const forEachIndex = remainingPath.findIndex(part => isForEachSymbol(part, aliasMap));
|
|
386
|
+
if (forEachIndex !== -1) {
|
|
387
|
+
// There's a nested @each
|
|
388
|
+
// Evaluate path up to the @each
|
|
389
|
+
const pathToForEach = remainingPath.slice(0, forEachIndex);
|
|
390
|
+
const pathAfterForEach = remainingPath.slice(forEachIndex + 1);
|
|
391
|
+
// Navigate to the nested iterable
|
|
392
|
+
let current = item;
|
|
393
|
+
for (const part of pathToForEach) {
|
|
394
|
+
if (withMethods.has(part)) {
|
|
395
|
+
const method = current[part];
|
|
396
|
+
if (typeof method === 'function') {
|
|
397
|
+
// For methods in the middle, we need to check the next part
|
|
398
|
+
const nextIndex = pathToForEach.indexOf(part) + 1;
|
|
399
|
+
const nextPart = pathToForEach[nextIndex];
|
|
400
|
+
if (nextPart && withMethods.has(nextPart)) {
|
|
401
|
+
current = method.call(current);
|
|
402
|
+
}
|
|
403
|
+
else if (nextPart) {
|
|
404
|
+
current = method.call(current, nextPart);
|
|
405
|
+
// Skip next part
|
|
406
|
+
pathToForEach.splice(nextIndex, 1);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
current = method.call(current);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
current = current[part];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
current = current[part];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Recursively apply to the nested iterable
|
|
421
|
+
if (isIterable(current)) {
|
|
422
|
+
applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// No nested @each, evaluate the remaining path normally
|
|
427
|
+
const result = evaluatePathWithMethods(item, remainingPath, value, withMethods);
|
|
428
|
+
if (result.isMethod) {
|
|
429
|
+
// Last segment is a method - call it
|
|
430
|
+
const method = result.target[result.lastKey];
|
|
431
|
+
if (typeof method === 'function') {
|
|
432
|
+
if (Array.isArray(value)) {
|
|
433
|
+
method.apply(result.target, value);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
method.call(result.target, value);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
// Normal assignment
|
|
442
|
+
const lastKey = result.lastKey;
|
|
443
|
+
const parent = result.target;
|
|
444
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
445
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
446
|
+
const currentValue = parent[lastKey];
|
|
447
|
+
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
448
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
449
|
+
}
|
|
450
|
+
assignGingerly(currentValue, value, options);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
parent[lastKey] = value;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
parent[lastKey] = value;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Apply alias substitutions to a key string.
|
|
465
|
+
* Replaces complete tokens between `?.` delimiters with their aliased values.
|
|
466
|
+
*
|
|
467
|
+
* @param key - The key string (e.g., '?.$?.my-element?.c?.+')
|
|
468
|
+
* @param aliasMap - Map of alias -> target name
|
|
469
|
+
* @returns The key with aliases substituted (e.g., '?.querySelector?.my-element?.classList?.add')
|
|
470
|
+
*/
|
|
471
|
+
function applyAliases(key, aliasMap) {
|
|
472
|
+
if (aliasMap.size === 0)
|
|
473
|
+
return key;
|
|
474
|
+
// Split by ?. to get tokens
|
|
475
|
+
const parts = key.split('?.');
|
|
476
|
+
// Apply aliases to each part
|
|
477
|
+
const substituted = parts.map(part => {
|
|
478
|
+
// Check if this exact part is an alias
|
|
479
|
+
return aliasMap.get(part) ?? part;
|
|
480
|
+
});
|
|
481
|
+
// Rejoin with ?.
|
|
482
|
+
return substituted.join('?.');
|
|
483
|
+
}
|
|
322
484
|
/**
|
|
323
485
|
* Main assignGingerly function
|
|
324
486
|
*/
|
|
@@ -332,12 +494,23 @@ export function assignGingerly(target, source, options) {
|
|
|
332
494
|
? options.withMethods
|
|
333
495
|
: new Set(options.withMethods)
|
|
334
496
|
: undefined;
|
|
497
|
+
// Convert aka object to Map for O(1) lookup and validate aliases
|
|
498
|
+
const aliasMap = new Map();
|
|
499
|
+
if (options?.aka) {
|
|
500
|
+
for (const [alias, target] of Object.entries(options.aka)) {
|
|
501
|
+
// Validate: disallow space and backtick in aliases
|
|
502
|
+
if (alias.includes(' ') || alias.includes('`')) {
|
|
503
|
+
throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
|
|
504
|
+
}
|
|
505
|
+
aliasMap.set(alias, target);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
335
508
|
const registry = options?.registry instanceof EnhancementRegistry
|
|
336
509
|
? options.registry
|
|
337
510
|
: options?.registry
|
|
338
511
|
? new options.registry()
|
|
339
512
|
: undefined;
|
|
340
|
-
// Convert Symbol.for string keys to actual symbols
|
|
513
|
+
// Convert Symbol.for string keys to actual symbols and apply aliases
|
|
341
514
|
const processedSource = {};
|
|
342
515
|
for (const key of Object.keys(source)) {
|
|
343
516
|
if (isSymbolForKey(key)) {
|
|
@@ -351,7 +524,9 @@ export function assignGingerly(target, source, options) {
|
|
|
351
524
|
}
|
|
352
525
|
}
|
|
353
526
|
else {
|
|
354
|
-
|
|
527
|
+
// Apply aliases to string keys
|
|
528
|
+
const substitutedKey = applyAliases(key, aliasMap);
|
|
529
|
+
processedSource[substitutedKey] = source[key];
|
|
355
530
|
}
|
|
356
531
|
}
|
|
357
532
|
// Copy over actual symbol keys
|
|
@@ -492,6 +667,50 @@ export function assignGingerly(target, source, options) {
|
|
|
492
667
|
}
|
|
493
668
|
if (isNestedPath(key)) {
|
|
494
669
|
const pathParts = parsePath(key);
|
|
670
|
+
// Check if path contains @each or @eachTime (forEach)
|
|
671
|
+
const forEachIndex = pathParts.findIndex(part => isForEachSymbol(part, aliasMap) || isReactiveForEachSymbol(part, aliasMap));
|
|
672
|
+
if (forEachIndex !== -1) {
|
|
673
|
+
// Check if it's reactive (@eachTime)
|
|
674
|
+
const isReactive = isReactiveForEachSymbol(pathParts[forEachIndex], aliasMap);
|
|
675
|
+
if (isReactive) {
|
|
676
|
+
// Reactive forEach - dynamic load and fire-and-forget
|
|
677
|
+
(async () => {
|
|
678
|
+
try {
|
|
679
|
+
const { handleEachTime } = await import('./eachTime.js');
|
|
680
|
+
await handleEachTime(target, pathParts, forEachIndex, value, withMethodsSet, aliasMap, options);
|
|
681
|
+
}
|
|
682
|
+
catch (error) {
|
|
683
|
+
console.error('Error in @eachTime:', error);
|
|
684
|
+
}
|
|
685
|
+
})();
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
// Static forEach (@each) - existing logic
|
|
689
|
+
const pathToForEach = pathParts.slice(0, forEachIndex);
|
|
690
|
+
const pathAfterForEach = pathParts.slice(forEachIndex + 1);
|
|
691
|
+
// Navigate to the iterable
|
|
692
|
+
let current = target;
|
|
693
|
+
if (pathToForEach.length > 0) {
|
|
694
|
+
if (withMethodsSet) {
|
|
695
|
+
const result = evaluatePathWithMethods(target, pathToForEach, value, withMethodsSet);
|
|
696
|
+
// The result.target is the current position after evaluating the path
|
|
697
|
+
// This is already the iterable we want
|
|
698
|
+
current = result.target;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
for (const part of pathToForEach) {
|
|
702
|
+
current = current[part];
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// Apply to each item in the iterable
|
|
707
|
+
if (isIterable(current)) {
|
|
708
|
+
applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options);
|
|
709
|
+
}
|
|
710
|
+
// If not iterable, let JavaScript throw error naturally when trying to iterate
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
// No @each in path - handle normally
|
|
495
714
|
// Check if we need to handle methods
|
|
496
715
|
if (withMethodsSet) {
|
|
497
716
|
const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
|
|
@@ -513,16 +732,16 @@ export function assignGingerly(target, source, options) {
|
|
|
513
732
|
const lastKey = result.lastKey;
|
|
514
733
|
const parent = result.target;
|
|
515
734
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
516
|
-
// Check if property exists and is readonly
|
|
517
|
-
if (lastKey in parent &&
|
|
735
|
+
// Check if property exists and is readonly
|
|
736
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
518
737
|
const currentValue = parent[lastKey];
|
|
519
738
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
520
|
-
throw new Error(`Cannot merge object into
|
|
739
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
521
740
|
}
|
|
522
741
|
assignGingerly(currentValue, value, options);
|
|
523
742
|
}
|
|
524
743
|
else {
|
|
525
|
-
// Property is writable
|
|
744
|
+
// Property is writable - replace it
|
|
526
745
|
parent[lastKey] = value;
|
|
527
746
|
}
|
|
528
747
|
}
|
|
@@ -535,18 +754,18 @@ export function assignGingerly(target, source, options) {
|
|
|
535
754
|
const lastKey = pathParts[pathParts.length - 1];
|
|
536
755
|
const parent = ensureNestedPath(target, pathParts);
|
|
537
756
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
538
|
-
// Check if property exists and is readonly
|
|
539
|
-
if (lastKey in parent &&
|
|
540
|
-
// Property is readonly
|
|
757
|
+
// Check if property exists and is readonly
|
|
758
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
759
|
+
// Property is readonly - check if current value is an object
|
|
541
760
|
const currentValue = parent[lastKey];
|
|
542
761
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
543
|
-
throw new Error(`Cannot merge object into
|
|
762
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
544
763
|
}
|
|
545
|
-
// Recursively apply assignGingerly to the readonly object
|
|
764
|
+
// Recursively apply assignGingerly to the readonly object
|
|
546
765
|
assignGingerly(currentValue, value, options);
|
|
547
766
|
}
|
|
548
767
|
else {
|
|
549
|
-
// Property is writable
|
|
768
|
+
// Property is writable - replace it
|
|
550
769
|
parent[lastKey] = value;
|
|
551
770
|
}
|
|
552
771
|
}
|
|
@@ -573,18 +792,18 @@ export function assignGingerly(target, source, options) {
|
|
|
573
792
|
}
|
|
574
793
|
// Normal assignment
|
|
575
794
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
576
|
-
// Check if property exists and is readonly
|
|
577
|
-
if (key in target &&
|
|
578
|
-
// Property is readonly
|
|
795
|
+
// Check if property exists and is readonly
|
|
796
|
+
if (key in target && isReadonlyProperty(target, key)) {
|
|
797
|
+
// Property is readonly - check if current value is an object
|
|
579
798
|
const currentValue = target[key];
|
|
580
799
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
581
|
-
throw new Error(`Cannot merge object into
|
|
800
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
|
|
582
801
|
}
|
|
583
|
-
// Recursively apply assignGingerly to the readonly object
|
|
802
|
+
// Recursively apply assignGingerly to the readonly object
|
|
584
803
|
assignGingerly(currentValue, value, options);
|
|
585
804
|
}
|
|
586
805
|
else {
|
|
587
|
-
// Property is writable
|
|
806
|
+
// Property is writable - replace it
|
|
588
807
|
target[key] = value;
|
|
589
808
|
}
|
|
590
809
|
}
|