assign-gingerly 0.0.32 → 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 +45 -123
- package/assignGingerly.js +25 -22
- package/assignGingerly.ts +25 -22
- package/eachTime.js +5 -5
- package/eachTime.ts +5 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ console.log(obj);
|
|
|
110
110
|
|
|
111
111
|
When the right hand side of an expression is an object, assignGingerly behavior depends on the context:
|
|
112
112
|
- For **nested paths** (starting with `?.`): recursively merges into nested objects, creating them if needed
|
|
113
|
-
- For **plain keys**: performs simple assignment (like `Object.assign`), unless the target property is readonly or
|
|
113
|
+
- For **plain keys**: performs simple assignment (like `Object.assign`), unless the target property is readonly or an accessor (see Examples 3a and 3b below)
|
|
114
114
|
|
|
115
115
|
Of course, just as Object.assign led to object spread notation, assignGingerly could lead to some sort of deep structural JavaScript syntax, but that is outside the scope of this polyfill package.
|
|
116
116
|
|
|
@@ -144,38 +144,40 @@ console.log(obj.config); // { theme: 'dark' } - intermediate object created
|
|
|
144
144
|
|
|
145
145
|
## Example 3a - Automatic Readonly Property Detection
|
|
146
146
|
|
|
147
|
-
assignGingerly automatically detects readonly properties and merges into them instead of attempting to replace them. This makes working with DOM properties like `
|
|
147
|
+
assignGingerly automatically detects readonly properties and merges into them instead of attempting to replace them. This makes working with DOM properties like `dataset` ergonomic:
|
|
148
148
|
|
|
149
149
|
```TypeScript
|
|
150
|
-
// Instead of this verbose syntax:
|
|
151
150
|
const div = document.createElement('div');
|
|
152
151
|
assignGingerly(div, {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// You can now use this cleaner syntax:
|
|
158
|
-
assignGingerly(div, {
|
|
159
|
-
style: {
|
|
160
|
-
height: '15px',
|
|
161
|
-
width: '20px'
|
|
152
|
+
dataset: {
|
|
153
|
+
userId: '123',
|
|
154
|
+
userName: 'Alice'
|
|
162
155
|
}
|
|
163
156
|
});
|
|
164
|
-
console.log(div.
|
|
165
|
-
console.log(div.
|
|
157
|
+
console.log(div.dataset.userId); // '123'
|
|
158
|
+
console.log(div.dataset.userName); // 'Alice'
|
|
166
159
|
```
|
|
167
160
|
|
|
168
161
|
**How it works:**
|
|
169
162
|
|
|
170
163
|
When assignGingerly encounters an object value being assigned to an existing property, it checks if that property is readonly:
|
|
171
164
|
- **Data properties** with `writable: false`
|
|
172
|
-
- **Accessor properties** with a getter but no setter
|
|
165
|
+
- **Accessor properties** with a getter but no setter (e.g., `dataset`, `shadowRoot`)
|
|
173
166
|
|
|
174
167
|
If the property is readonly and its current value is an object, assignGingerly automatically merges into it recursively.
|
|
175
168
|
|
|
176
|
-
**
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
**Note on `element.style`:** The `style` property has both a getter and a setter, so it is *not* treated as readonly. Use nested path syntax instead:
|
|
170
|
+
|
|
171
|
+
```TypeScript
|
|
172
|
+
// Use nested path syntax for style
|
|
173
|
+
assignGingerly(div, {
|
|
174
|
+
'?.style?.height': '15px',
|
|
175
|
+
'?.style?.width': '20px'
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Examples of readonly properties that trigger merging:**
|
|
180
|
+
- `HTMLElement.dataset` - getter only, no setter
|
|
179
181
|
- Custom objects with `Object.defineProperty(obj, 'prop', { value: {}, writable: false })`
|
|
180
182
|
- Accessor properties with getter only: `Object.defineProperty(obj, 'prop', { get() { return {}; } })`
|
|
181
183
|
|
|
@@ -225,134 +227,54 @@ assignGingerly(config, {
|
|
|
225
227
|
console.log(config.settings.theme); // 'dark'
|
|
226
228
|
```
|
|
227
229
|
|
|
228
|
-
## Example 3b -
|
|
230
|
+
## Example 3b - Class Instances Are Replaced
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
Unlike readonly/accessor properties, class instances on writable properties are **replaced** by simple assignment, just like plain objects. This allows you to swap one object for another without unexpected merging:
|
|
231
233
|
|
|
232
234
|
```TypeScript
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
constructor(element, ctx, initVals) {
|
|
238
|
-
this.element = element;
|
|
239
|
-
this.instanceId = Math.random(); // Track instance identity
|
|
240
|
-
if (initVals) {
|
|
241
|
-
Object.assign(this, initVals);
|
|
242
|
-
}
|
|
235
|
+
class FakeDocumentFragment {
|
|
236
|
+
constructor() {
|
|
237
|
+
this.nodeType = 11;
|
|
238
|
+
this.childNodes = [];
|
|
243
239
|
}
|
|
244
|
-
prop1 = null;
|
|
245
|
-
prop2 = null;
|
|
246
240
|
}
|
|
247
241
|
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
myEnh: new MyEnhancement(element, {}, {})
|
|
242
|
+
const obj = {
|
|
243
|
+
clone: new FakeDocumentFragment()
|
|
251
244
|
};
|
|
252
245
|
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
// Clean syntax - no need for ?.myEnh?.prop1 notation
|
|
256
|
-
assignGingerly(element, {
|
|
257
|
-
enh: {
|
|
258
|
-
myEnh: {
|
|
259
|
-
prop1: 'value1',
|
|
260
|
-
prop2: 'value2'
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
console.log(element.enh.myEnh.instanceId === originalId); // true - instance preserved!
|
|
266
|
-
console.log(element.enh.myEnh.prop1); // 'value1'
|
|
267
|
-
console.log(element.enh.myEnh.prop2); // 'value2'
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
**How it works:**
|
|
271
|
-
|
|
272
|
-
When assignGingerly encounters an object value being assigned to an existing property, it checks if the current value is a class instance (not a plain object):
|
|
273
|
-
|
|
274
|
-
- **Class instances** are detected by checking if their prototype is something other than `Object.prototype` or `null`
|
|
275
|
-
- **Plain objects** `{}` have `Object.prototype` as their prototype
|
|
276
|
-
- **Class instances** have their class's prototype
|
|
277
|
-
|
|
278
|
-
If the existing value is a class instance, assignGingerly merges into it instead of replacing it.
|
|
279
|
-
|
|
280
|
-
**What counts as a class instance:**
|
|
281
|
-
- Custom class instances: `new MyClass()`
|
|
282
|
-
- Built-in class instances: `new Date()`, `new Map()`, `new Set()`, etc.
|
|
283
|
-
- Enhancement instances on the `enh` property
|
|
284
|
-
- Any object whose prototype is not `Object.prototype` or `null`
|
|
285
|
-
|
|
286
|
-
**What doesn't count:**
|
|
287
|
-
- Plain objects: `{}`, `{ a: 1 }`
|
|
288
|
-
- Arrays: `[]`, `[1, 2, 3]` (arrays are replaced, not merged)
|
|
289
|
-
- Primitives: strings, numbers, booleans
|
|
290
|
-
|
|
291
|
-
**Benefits:**
|
|
292
|
-
|
|
293
|
-
This feature enables clean, framework-friendly syntax for updating enhancements:
|
|
246
|
+
const element = document.createElement('div');
|
|
294
247
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
'?.enh?.mellowYellow?.madAboutFourteen': true
|
|
248
|
+
// Replace the DocumentFragment with the actual element
|
|
249
|
+
assignGingerly(obj, {
|
|
250
|
+
clone: element
|
|
299
251
|
});
|
|
300
252
|
|
|
301
|
-
//
|
|
302
|
-
assignGingerly(element, {
|
|
303
|
-
enh: {
|
|
304
|
-
mellowYellow: {
|
|
305
|
-
madAboutFourteen: true
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
});
|
|
253
|
+
console.log(obj.clone === element); // true - replaced, not merged
|
|
309
254
|
```
|
|
310
255
|
|
|
311
|
-
**
|
|
312
|
-
|
|
313
|
-
```TypeScript
|
|
314
|
-
// Multiple enhancements at once
|
|
315
|
-
assignGingerly(element, {
|
|
316
|
-
enh: {
|
|
317
|
-
enhancement1: { prop: 'value1' },
|
|
318
|
-
enhancement2: { prop: 'value2' }
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Works with built-in classes too
|
|
323
|
-
const obj = {
|
|
324
|
-
timestamp: new Date('2024-01-01')
|
|
325
|
-
};
|
|
256
|
+
**Why replacement instead of merging?**
|
|
326
257
|
|
|
327
|
-
|
|
328
|
-
timestamp: {
|
|
329
|
-
customProp: 'metadata'
|
|
330
|
-
}
|
|
331
|
-
});
|
|
258
|
+
In real-world use cases, you often need to replace one object with another of a completely different type. For example, replacing a cloned DocumentFragment with the actual web component element. Automatic merging would corrupt the target by mixing properties from incompatible types.
|
|
332
259
|
|
|
333
|
-
|
|
334
|
-
console.log(obj.timestamp.customProp); // 'metadata'
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
**Combined with readonly detection:**
|
|
260
|
+
**Readonly/accessor properties are still merged:**
|
|
338
261
|
|
|
339
|
-
|
|
262
|
+
The distinction is clear:
|
|
263
|
+
- **Writable data properties**: always replaced (whether holding a plain object or class instance)
|
|
264
|
+
- **Readonly data properties** (`writable: false`): merged into
|
|
265
|
+
- **Getter-only accessor properties** (no setter): merged into
|
|
266
|
+
- **Getter+setter accessor properties** (e.g., `style`): setter runs with the value as-is
|
|
340
267
|
|
|
341
268
|
```TypeScript
|
|
342
269
|
const div = document.createElement('div');
|
|
343
|
-
div.enh = {
|
|
344
|
-
myEnh: new MyEnhancement(div, {}, {})
|
|
345
|
-
};
|
|
346
270
|
|
|
347
271
|
assignGingerly(div, {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
myEnh: { prop: 'value' } // Class instance - merged
|
|
351
|
-
},
|
|
352
|
-
dataset: { userId: '123' } // Readonly - merged
|
|
272
|
+
dataset: { userId: '123' }, // Getter-only - merged
|
|
273
|
+
'?.style?.height': '100px' // Use nested path for style
|
|
353
274
|
});
|
|
354
275
|
|
|
355
|
-
//
|
|
276
|
+
console.log(div.dataset.userId); // '123'
|
|
277
|
+
console.log(div.style.height); // '100px'
|
|
356
278
|
```
|
|
357
279
|
|
|
358
280
|
## Example 3c - Method Calls with withMethods
|
package/assignGingerly.js
CHANGED
|
@@ -228,11 +228,14 @@ 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
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
|
+
*
|
|
236
239
|
* Exported for use by eachTime.ts
|
|
237
240
|
*/
|
|
238
241
|
export function isReadonlyProperty(obj, propName) {
|
|
@@ -253,8 +256,8 @@ export function isReadonlyProperty(obj, propName) {
|
|
|
253
256
|
if ('value' in descriptor) {
|
|
254
257
|
return descriptor.writable === false;
|
|
255
258
|
}
|
|
256
|
-
// If it's an accessor property
|
|
257
|
-
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) {
|
|
258
261
|
return descriptor.set === undefined;
|
|
259
262
|
}
|
|
260
263
|
return false;
|
|
@@ -439,10 +442,10 @@ function applyToEach(iterable, remainingPath, value, withMethods, aliasMap, opti
|
|
|
439
442
|
const lastKey = result.lastKey;
|
|
440
443
|
const parent = result.target;
|
|
441
444
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
442
|
-
if (lastKey in parent &&
|
|
445
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
443
446
|
const currentValue = parent[lastKey];
|
|
444
447
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
445
|
-
throw new Error(`Cannot merge object into
|
|
448
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
446
449
|
}
|
|
447
450
|
assignGingerly(currentValue, value, options);
|
|
448
451
|
}
|
|
@@ -729,16 +732,16 @@ export function assignGingerly(target, source, options) {
|
|
|
729
732
|
const lastKey = result.lastKey;
|
|
730
733
|
const parent = result.target;
|
|
731
734
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
732
|
-
// Check if property exists and is readonly
|
|
733
|
-
if (lastKey in parent &&
|
|
735
|
+
// Check if property exists and is readonly
|
|
736
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
734
737
|
const currentValue = parent[lastKey];
|
|
735
738
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
736
|
-
throw new Error(`Cannot merge object into
|
|
739
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
737
740
|
}
|
|
738
741
|
assignGingerly(currentValue, value, options);
|
|
739
742
|
}
|
|
740
743
|
else {
|
|
741
|
-
// Property is writable
|
|
744
|
+
// Property is writable - replace it
|
|
742
745
|
parent[lastKey] = value;
|
|
743
746
|
}
|
|
744
747
|
}
|
|
@@ -751,18 +754,18 @@ export function assignGingerly(target, source, options) {
|
|
|
751
754
|
const lastKey = pathParts[pathParts.length - 1];
|
|
752
755
|
const parent = ensureNestedPath(target, pathParts);
|
|
753
756
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
754
|
-
// Check if property exists and is readonly
|
|
755
|
-
if (lastKey in parent &&
|
|
756
|
-
// 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
|
|
757
760
|
const currentValue = parent[lastKey];
|
|
758
761
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
759
|
-
throw new Error(`Cannot merge object into
|
|
762
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
760
763
|
}
|
|
761
|
-
// Recursively apply assignGingerly to the readonly object
|
|
764
|
+
// Recursively apply assignGingerly to the readonly object
|
|
762
765
|
assignGingerly(currentValue, value, options);
|
|
763
766
|
}
|
|
764
767
|
else {
|
|
765
|
-
// Property is writable
|
|
768
|
+
// Property is writable - replace it
|
|
766
769
|
parent[lastKey] = value;
|
|
767
770
|
}
|
|
768
771
|
}
|
|
@@ -789,18 +792,18 @@ export function assignGingerly(target, source, options) {
|
|
|
789
792
|
}
|
|
790
793
|
// Normal assignment
|
|
791
794
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
792
|
-
// Check if property exists and is readonly
|
|
793
|
-
if (key in target &&
|
|
794
|
-
// 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
|
|
795
798
|
const currentValue = target[key];
|
|
796
799
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
797
|
-
throw new Error(`Cannot merge object into
|
|
800
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
|
|
798
801
|
}
|
|
799
|
-
// Recursively apply assignGingerly to the readonly object
|
|
802
|
+
// Recursively apply assignGingerly to the readonly object
|
|
800
803
|
assignGingerly(currentValue, value, options);
|
|
801
804
|
}
|
|
802
805
|
else {
|
|
803
|
-
// Property is writable
|
|
806
|
+
// Property is writable - replace it
|
|
804
807
|
target[key] = value;
|
|
805
808
|
}
|
|
806
809
|
}
|
package/assignGingerly.ts
CHANGED
|
@@ -356,11 +356,14 @@ function ensureNestedPath(obj: any, pathParts: string[]): any {
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
/**
|
|
359
|
-
* Helper function to check if a property
|
|
360
|
-
* A property is
|
|
359
|
+
* Helper function to check if a property should be merged into rather than replaced.
|
|
360
|
+
* A property is non-replaceable if:
|
|
361
361
|
* - It's a data property with writable: false, OR
|
|
362
362
|
* - It's an accessor property with a getter but no setter
|
|
363
363
|
*
|
|
364
|
+
* Properties with both a getter and setter (e.g., element.style) are treated as
|
|
365
|
+
* replaceable — the setter runs with whatever value is provided (garbage in, garbage out).
|
|
366
|
+
*
|
|
364
367
|
* Exported for use by eachTime.ts
|
|
365
368
|
*/
|
|
366
369
|
export function isReadonlyProperty(obj: any, propName: string | symbol): boolean {
|
|
@@ -383,8 +386,8 @@ export function isReadonlyProperty(obj: any, propName: string | symbol): boolean
|
|
|
383
386
|
return descriptor.writable === false;
|
|
384
387
|
}
|
|
385
388
|
|
|
386
|
-
// If it's an accessor property
|
|
387
|
-
if ('get' in descriptor) {
|
|
389
|
+
// If it's an accessor property with a getter but no setter, it's readonly
|
|
390
|
+
if ('get' in descriptor && descriptor.get !== undefined) {
|
|
388
391
|
return descriptor.set === undefined;
|
|
389
392
|
}
|
|
390
393
|
|
|
@@ -589,10 +592,10 @@ function applyToEach(
|
|
|
589
592
|
const parent = result.target;
|
|
590
593
|
|
|
591
594
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
592
|
-
if (lastKey in parent &&
|
|
595
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
593
596
|
const currentValue = parent[lastKey];
|
|
594
597
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
595
|
-
throw new Error(`Cannot merge object into
|
|
598
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
596
599
|
}
|
|
597
600
|
assignGingerly(currentValue, value, options);
|
|
598
601
|
} else {
|
|
@@ -918,15 +921,15 @@ export function assignGingerly(
|
|
|
918
921
|
const parent = result.target;
|
|
919
922
|
|
|
920
923
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
921
|
-
// Check if property exists and is readonly
|
|
922
|
-
if (lastKey in parent &&
|
|
924
|
+
// Check if property exists and is readonly
|
|
925
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
923
926
|
const currentValue = parent[lastKey];
|
|
924
927
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
925
|
-
throw new Error(`Cannot merge object into
|
|
928
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
926
929
|
}
|
|
927
930
|
assignGingerly(currentValue, value, options);
|
|
928
931
|
} else {
|
|
929
|
-
// Property is writable
|
|
932
|
+
// Property is writable - replace it
|
|
930
933
|
parent[lastKey] = value;
|
|
931
934
|
}
|
|
932
935
|
} else {
|
|
@@ -938,17 +941,17 @@ export function assignGingerly(
|
|
|
938
941
|
const parent = ensureNestedPath(target, pathParts);
|
|
939
942
|
|
|
940
943
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
941
|
-
// Check if property exists and is readonly
|
|
942
|
-
if (lastKey in parent &&
|
|
943
|
-
// Property is readonly
|
|
944
|
+
// Check if property exists and is readonly
|
|
945
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
946
|
+
// Property is readonly - check if current value is an object
|
|
944
947
|
const currentValue = parent[lastKey];
|
|
945
948
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
946
|
-
throw new Error(`Cannot merge object into
|
|
949
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
947
950
|
}
|
|
948
|
-
// Recursively apply assignGingerly to the readonly object
|
|
951
|
+
// Recursively apply assignGingerly to the readonly object
|
|
949
952
|
assignGingerly(currentValue, value, options);
|
|
950
953
|
} else {
|
|
951
|
-
// Property is writable
|
|
954
|
+
// Property is writable - replace it
|
|
952
955
|
parent[lastKey] = value;
|
|
953
956
|
}
|
|
954
957
|
} else {
|
|
@@ -974,17 +977,17 @@ export function assignGingerly(
|
|
|
974
977
|
|
|
975
978
|
// Normal assignment
|
|
976
979
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
977
|
-
// Check if property exists and is readonly
|
|
978
|
-
if (key in target &&
|
|
979
|
-
// Property is readonly
|
|
980
|
+
// Check if property exists and is readonly
|
|
981
|
+
if (key in target && isReadonlyProperty(target, key)) {
|
|
982
|
+
// Property is readonly - check if current value is an object
|
|
980
983
|
const currentValue = target[key];
|
|
981
984
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
982
|
-
throw new Error(`Cannot merge object into
|
|
985
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
|
|
983
986
|
}
|
|
984
|
-
// Recursively apply assignGingerly to the readonly object
|
|
987
|
+
// Recursively apply assignGingerly to the readonly object
|
|
985
988
|
assignGingerly(currentValue, value, options);
|
|
986
989
|
} else {
|
|
987
|
-
// Property is writable
|
|
990
|
+
// Property is writable - replace it
|
|
988
991
|
target[key] = value;
|
|
989
992
|
}
|
|
990
993
|
} else {
|
package/eachTime.js
CHANGED
|
@@ -58,7 +58,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
58
58
|
(async () => {
|
|
59
59
|
try {
|
|
60
60
|
// Import needed functions from assignGingerly
|
|
61
|
-
const { evaluatePathWithMethods, assignGingerly, isReadonlyProperty
|
|
61
|
+
const { evaluatePathWithMethods, assignGingerly, isReadonlyProperty } = await import('./assignGingerly.js');
|
|
62
62
|
if (pathAfterForEach.length > 0) {
|
|
63
63
|
const result = evaluatePathWithMethods(mountedElement, pathAfterForEach, value, withMethods || new Set());
|
|
64
64
|
if (result.isMethod) {
|
|
@@ -78,17 +78,17 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
78
78
|
const lastKey = result.lastKey;
|
|
79
79
|
const parent = result.target;
|
|
80
80
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
81
|
-
// Check if property exists and is readonly
|
|
82
|
-
if (lastKey in parent &&
|
|
81
|
+
// Check if property exists and is readonly
|
|
82
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
83
83
|
const currentValue = parent[lastKey];
|
|
84
84
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
85
|
-
throw new Error(`Cannot merge object into
|
|
85
|
+
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
86
86
|
}
|
|
87
87
|
// Recursively apply assignGingerly
|
|
88
88
|
assignGingerly(currentValue, value, options);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
// Property is writable
|
|
91
|
+
// Property is writable - replace it
|
|
92
92
|
parent[lastKey] = value;
|
|
93
93
|
}
|
|
94
94
|
}
|
package/eachTime.ts
CHANGED
|
@@ -77,8 +77,7 @@ export async function handleEachTime(
|
|
|
77
77
|
const {
|
|
78
78
|
evaluatePathWithMethods,
|
|
79
79
|
assignGingerly,
|
|
80
|
-
isReadonlyProperty
|
|
81
|
-
isClassInstance
|
|
80
|
+
isReadonlyProperty
|
|
82
81
|
} = await import('./assignGingerly.js');
|
|
83
82
|
|
|
84
83
|
if (pathAfterForEach.length > 0) {
|
|
@@ -105,18 +104,18 @@ export async function handleEachTime(
|
|
|
105
104
|
const parent = result.target;
|
|
106
105
|
|
|
107
106
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
108
|
-
// Check if property exists and is readonly
|
|
109
|
-
if (lastKey in parent &&
|
|
107
|
+
// Check if property exists and is readonly
|
|
108
|
+
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
110
109
|
const currentValue = parent[lastKey];
|
|
111
110
|
if (typeof currentValue !== 'object' || currentValue === null) {
|
|
112
111
|
throw new Error(
|
|
113
|
-
`Cannot merge object into
|
|
112
|
+
`Cannot merge object into readonly primitive property '${String(lastKey)}'`
|
|
114
113
|
);
|
|
115
114
|
}
|
|
116
115
|
// Recursively apply assignGingerly
|
|
117
116
|
assignGingerly(currentValue, value, options);
|
|
118
117
|
} else {
|
|
119
|
-
// Property is writable
|
|
118
|
+
// Property is writable - replace it
|
|
120
119
|
parent[lastKey] = value;
|
|
121
120
|
}
|
|
122
121
|
} else {
|
package/package.json
CHANGED