assign-gingerly 0.0.11 → 0.0.13

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/assignGingerly.js CHANGED
@@ -344,9 +344,11 @@ export function assignGingerly(target, source, options) {
344
344
  }
345
345
  }
346
346
  // Find the mapped property name
347
- const mappedKey = registryItem.symlinks[sym];
348
- if (mappedKey && instance && typeof instance === 'object') {
349
- instance[mappedKey] = value;
347
+ if (registryItem.symlinks) {
348
+ const mappedKey = registryItem.symlinks[sym];
349
+ if (mappedKey && instance && typeof instance === 'object') {
350
+ instance[mappedKey] = value;
351
+ }
350
352
  }
351
353
  }
352
354
  }
@@ -397,9 +399,11 @@ export function assignGingerly(target, source, options) {
397
399
  target.enh[registryItem.enhKey] = instance;
398
400
  }
399
401
  }
400
- const mappedKey = registryItem.symlinks[prop];
401
- if (mappedKey && instance && typeof instance === 'object') {
402
- instance[mappedKey] = value;
402
+ if (registryItem.symlinks) {
403
+ const mappedKey = registryItem.symlinks[prop];
404
+ if (mappedKey && instance && typeof instance === 'object') {
405
+ instance[mappedKey] = value;
406
+ }
403
407
  }
404
408
  }
405
409
  }
package/assignGingerly.ts CHANGED
@@ -1,22 +1,6 @@
1
- /**
2
- * Configuration for enhancing elements with class instances
3
- */
4
- export interface EnhancementConfig<T = any> {
5
- spawn: {
6
- new (oElement?: Element, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
7
- canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
8
- };
9
- symlinks: { [key: string | symbol]: keyof T };
10
- enhKey?: string;
11
- lifecycleKeys?: {
12
- dispose?: string;
13
- resolved?: string;
14
- };
15
- }
16
1
 
17
- export interface SpawnContext<T = any> {
18
- config: EnhancementConfig<T>;
19
- }
2
+
3
+ import { EnhancementConfig } from "./types";
20
4
 
21
5
  /**
22
6
  * @deprecated Use EnhancementConfig instead
@@ -411,10 +395,13 @@ export function assignGingerly(
411
395
  }
412
396
 
413
397
  // Find the mapped property name
414
- const mappedKey = registryItem.symlinks[sym];
415
- if (mappedKey && instance && typeof instance === 'object') {
416
- (instance as any)[mappedKey] = value;
398
+ if(registryItem.symlinks){
399
+ const mappedKey = registryItem.symlinks[sym];
400
+ if (mappedKey && instance && typeof instance === 'object') {
401
+ (instance as any)[mappedKey] = value;
402
+ }
417
403
  }
404
+
418
405
  }
419
406
  }
420
407
  }
@@ -471,11 +458,13 @@ export function assignGingerly(
471
458
  (target as any).enh[registryItem.enhKey] = instance;
472
459
  }
473
460
  }
474
-
475
- const mappedKey = registryItem.symlinks[prop];
476
- if (mappedKey && instance && typeof instance === 'object') {
477
- (instance as any)[mappedKey] = value;
461
+ if(registryItem.symlinks){
462
+ const mappedKey = registryItem.symlinks[prop];
463
+ if (mappedKey && instance && typeof instance === 'object') {
464
+ (instance as any)[mappedKey] = value;
465
+ }
478
466
  }
467
+
479
468
  }
480
469
  }
481
470
  return true;
package/index.js CHANGED
@@ -2,4 +2,6 @@ export { assignGingerly } from './assignGingerly.js';
2
2
  export { assignTentatively } from './assignTentatively.js';
3
3
  export { BaseRegistry } from './assignGingerly.js';
4
4
  export { waitForEvent } from './waitForEvent.js';
5
+ export { ParserRegistry, globalParserRegistry } from './parserRegistry.js';
6
+ export { parseWithAttrs } from './parseWithAttrs.js';
5
7
  import './object-extension.js';
package/index.ts CHANGED
@@ -2,4 +2,6 @@ export {assignGingerly} from './assignGingerly.js';
2
2
  export {assignTentatively} from './assignTentatively.js';
3
3
  export {BaseRegistry} from './assignGingerly.js';
4
4
  export {waitForEvent} from './waitForEvent.js';
5
+ export {ParserRegistry, globalParserRegistry} from './parserRegistry.js';
6
+ export {parseWithAttrs} from './parseWithAttrs.js';
5
7
  import './object-extension.js';
@@ -48,9 +48,10 @@ class ElementEnhancementContainer {
48
48
  /**
49
49
  * Get or spawn an instance for a registry item
50
50
  * @param registryItem - The registry item to get/spawn instance for
51
+ * @param mountCtx - Optional context to pass to the spawned instance
51
52
  * @returns The spawned instance
52
53
  */
53
- get(registryItem) {
54
+ get(registryItem, mountCtx) {
54
55
  const element = this.element;
55
56
  // Get the registry from customElementRegistry
56
57
  const registry = element.customElementRegistry?.enhancementRegistry;
@@ -75,7 +76,7 @@ class ElementEnhancementContainer {
75
76
  const SpawnClass = registryItem.spawn;
76
77
  // Check canSpawn if it exists
77
78
  if (typeof SpawnClass.canSpawn === 'function') {
78
- const ctx = { config: registryItem };
79
+ const ctx = { config: registryItem, mountCtx };
79
80
  if (!SpawnClass.canSpawn(element, ctx)) {
80
81
  // canSpawn returned false, return undefined
81
82
  return undefined;
@@ -83,7 +84,7 @@ class ElementEnhancementContainer {
83
84
  }
84
85
  // Check if there's an enhKey
85
86
  if (registryItem.enhKey) {
86
- const ctx = { config: registryItem };
87
+ const ctx = { config: registryItem, mountCtx };
87
88
  const self = this;
88
89
  // Parse attributes if withAttrs is defined
89
90
  let attrInitVals = undefined;
@@ -111,7 +112,7 @@ class ElementEnhancementContainer {
111
112
  }
112
113
  else {
113
114
  // No enhKey, just spawn with element
114
- const ctx = { config: registryItem };
115
+ const ctx = { config: registryItem, mountCtx };
115
116
  instance = new SpawnClass(element, ctx);
116
117
  }
117
118
  // Store in global instance map
@@ -152,16 +153,17 @@ class ElementEnhancementContainer {
152
153
  /**
153
154
  * Wait for an enhancement instance to be resolved
154
155
  * @param registryItem - The registry item to wait for
156
+ * @param mountCtx - Optional context to pass to the spawned instance
155
157
  * @returns Promise that resolves with the spawned instance
156
158
  */
157
- async whenResolved(registryItem) {
159
+ async whenResolved(registryItem, mountCtx) {
158
160
  const lifecycleKeys = normalizeLifecycleKeys(registryItem?.lifecycleKeys);
159
161
  const resolvedKey = lifecycleKeys?.resolved;
160
162
  if (resolvedKey === undefined) {
161
163
  throw new Error('Must specify resolved key in lifecycleKeys');
162
164
  }
163
- // Get or spawn the instance
164
- const spawnedInstance = this.get(registryItem);
165
+ // Get or spawn the instance (pass mountCtx through)
166
+ const spawnedInstance = this.get(registryItem, mountCtx);
165
167
  // Check if already resolved
166
168
  if (spawnedInstance[resolvedKey]) {
167
169
  return spawnedInstance;
@@ -114,9 +114,10 @@ class ElementEnhancementContainer {
114
114
  /**
115
115
  * Get or spawn an instance for a registry item
116
116
  * @param registryItem - The registry item to get/spawn instance for
117
+ * @param mountCtx - Optional context to pass to the spawned instance
117
118
  * @returns The spawned instance
118
119
  */
119
- get(registryItem: any): any {
120
+ get(registryItem: any, mountCtx?: any): any {
120
121
  const element = this.element;
121
122
 
122
123
  // Get the registry from customElementRegistry
@@ -148,7 +149,7 @@ class ElementEnhancementContainer {
148
149
 
149
150
  // Check canSpawn if it exists
150
151
  if (typeof SpawnClass.canSpawn === 'function') {
151
- const ctx = { config: registryItem };
152
+ const ctx = { config: registryItem, mountCtx };
152
153
  if (!SpawnClass.canSpawn(element, ctx)) {
153
154
  // canSpawn returned false, return undefined
154
155
  return undefined;
@@ -157,7 +158,7 @@ class ElementEnhancementContainer {
157
158
 
158
159
  // Check if there's an enhKey
159
160
  if (registryItem.enhKey) {
160
- const ctx = { config: registryItem };
161
+ const ctx = { config: registryItem, mountCtx };
161
162
  const self = this as any;
162
163
 
163
164
  // Parse attributes if withAttrs is defined
@@ -192,7 +193,7 @@ class ElementEnhancementContainer {
192
193
  self[registryItem.enhKey] = instance;
193
194
  } else {
194
195
  // No enhKey, just spawn with element
195
- const ctx = { config: registryItem };
196
+ const ctx = { config: registryItem, mountCtx };
196
197
  instance = new SpawnClass(element, ctx);
197
198
  }
198
199
 
@@ -243,9 +244,10 @@ class ElementEnhancementContainer {
243
244
  /**
244
245
  * Wait for an enhancement instance to be resolved
245
246
  * @param registryItem - The registry item to wait for
247
+ * @param mountCtx - Optional context to pass to the spawned instance
246
248
  * @returns Promise that resolves with the spawned instance
247
249
  */
248
- async whenResolved(registryItem: any): Promise<any> {
250
+ async whenResolved(registryItem: any, mountCtx?: any): Promise<any> {
249
251
  const lifecycleKeys = normalizeLifecycleKeys(registryItem?.lifecycleKeys);
250
252
  const resolvedKey = lifecycleKeys?.resolved;
251
253
 
@@ -253,8 +255,8 @@ class ElementEnhancementContainer {
253
255
  throw new Error('Must specify resolved key in lifecycleKeys');
254
256
  }
255
257
 
256
- // Get or spawn the instance
257
- const spawnedInstance = this.get(registryItem);
258
+ // Get or spawn the instance (pass mountCtx through)
259
+ const spawnedInstance = this.get(registryItem, mountCtx);
258
260
 
259
261
  // Check if already resolved
260
262
  if ((spawnedInstance as any)[resolvedKey]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
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": {
@@ -35,6 +35,13 @@
35
35
  },
36
36
  "./waitForEvent.js": {
37
37
  "default": "./waitForEvent.js"
38
+ },
39
+ "./parserRegistry.js": {
40
+ "default": "./parserRegistry.js"
41
+ },
42
+ "./parseWithAttrs.js": {
43
+ "default": "./parseWithAttrs.js",
44
+ "types": "./types.d.ts"
38
45
  }
39
46
  },
40
47
  "main": "index.js",
package/parseWithAttrs.js CHANGED
@@ -1,3 +1,121 @@
1
+ import { globalParserRegistry } from './parserRegistry.js';
2
+ // Module-level cache for parsed attribute values
3
+ // Structure: Map<configKey, Map<attrValue, parsedValue>>
4
+ const parseCache = new Map();
5
+ /**
6
+ * Resolves a parser specification to an actual parser function
7
+ * Supports:
8
+ * - Inline functions (direct use)
9
+ * - Named parsers from global registry
10
+ * - Custom element static methods (element-name.methodName)
11
+ *
12
+ * @param parserSpec - Parser function or string reference
13
+ * @returns The resolved parser function
14
+ * @throws Error if parser cannot be resolved
15
+ */
16
+ function resolveParser(parserSpec) {
17
+ // Undefined - no parser specified
18
+ if (parserSpec === undefined) {
19
+ return undefined;
20
+ }
21
+ // Inline function - use directly
22
+ if (typeof parserSpec === 'function') {
23
+ return parserSpec;
24
+ }
25
+ // String reference - resolve it
26
+ if (typeof parserSpec === 'string') {
27
+ // Check if it's a custom element reference (contains dot)
28
+ if (parserSpec.includes('.')) {
29
+ const dotIndex = parserSpec.indexOf('.');
30
+ const elementName = parserSpec.substring(0, dotIndex);
31
+ const methodName = parserSpec.substring(dotIndex + 1);
32
+ // Try custom element lookup
33
+ if (typeof customElements !== 'undefined') {
34
+ try {
35
+ const ctr = customElements.get(elementName);
36
+ if (ctr && typeof ctr[methodName] === 'function') {
37
+ return ctr[methodName];
38
+ }
39
+ }
40
+ catch (e) {
41
+ // customElements.get might throw, fall through to registry
42
+ }
43
+ }
44
+ // Fall through to global registry (allows dot notation in registry too)
45
+ }
46
+ // Try global registry
47
+ const parser = globalParserRegistry.get(parserSpec);
48
+ if (parser) {
49
+ return parser;
50
+ }
51
+ // Not found anywhere
52
+ throw new Error(`Parser "${parserSpec}" not found. ` +
53
+ `Check that it's registered in globalParserRegistry or exists as a static method on the custom element.`);
54
+ }
55
+ return undefined;
56
+ }
57
+ /**
58
+ * Creates a cache key from an AttrConfig
59
+ * Includes instanceOf and parser identifier to ensure correct cache hits
60
+ */
61
+ function getCacheKey(config) {
62
+ const instanceOfStr = typeof config.instanceOf === 'function'
63
+ ? config.instanceOf.name
64
+ : (config.instanceOf || 'default');
65
+ // Include parser in cache key
66
+ let parserStr;
67
+ if (config.parser === undefined) {
68
+ parserStr = 'builtin';
69
+ }
70
+ else if (typeof config.parser === 'string') {
71
+ parserStr = `named:${config.parser}`;
72
+ }
73
+ else {
74
+ parserStr = 'custom';
75
+ }
76
+ return `${instanceOfStr}|${parserStr}`;
77
+ }
78
+ /**
79
+ * Gets a cached parsed value or parses and caches it
80
+ * @param attrValue - The attribute value to parse (or null)
81
+ * @param config - The attribute configuration
82
+ * @param parser - The parser function to use
83
+ * @returns The parsed value
84
+ */
85
+ function parseWithCache(attrValue, config, parser) {
86
+ // Skip caching for Boolean (presence check doesn't benefit from caching)
87
+ if (config.instanceOf === 'Boolean') {
88
+ return parser(attrValue);
89
+ }
90
+ // Get or create cache for this config
91
+ const cacheKey = getCacheKey(config);
92
+ if (!parseCache.has(cacheKey)) {
93
+ parseCache.set(cacheKey, new Map());
94
+ }
95
+ const valueCache = parseCache.get(cacheKey);
96
+ // Use special key for null values
97
+ const valueCacheKey = attrValue === null ? '__NULL__' : attrValue;
98
+ // Check if we have a cached value
99
+ if (valueCache.has(valueCacheKey)) {
100
+ const cachedValue = valueCache.get(valueCacheKey);
101
+ // Return clone if requested
102
+ if (config.parseCache === 'cloned') {
103
+ // Use structuredClone for deep cloning
104
+ return structuredClone(cachedValue);
105
+ }
106
+ // Return shared reference
107
+ return cachedValue;
108
+ }
109
+ // Parse the value
110
+ const parsedValue = parser(attrValue);
111
+ // Store in cache
112
+ valueCache.set(valueCacheKey, parsedValue);
113
+ // Return clone if requested (even on first parse)
114
+ if (config.parseCache === 'cloned') {
115
+ return structuredClone(parsedValue);
116
+ }
117
+ return parsedValue;
118
+ }
1
119
  /**
2
120
  * Checks if a string contains a dash or non-ASCII character
3
121
  */
@@ -188,14 +306,40 @@ export function parseWithAttrs(element, attrPatterns, allowUnprefixed) {
188
306
  // Second pass: read attributes and parse values
189
307
  for (const [key, { attrName, config }] of resolvedAttrs) {
190
308
  const attrValue = getAttributeValue(element, attrName, allowUnprefixed);
191
- // Skip if attribute doesn't exist
192
- if (attrValue === null && config.instanceOf !== 'Boolean') {
309
+ // Handle missing attribute
310
+ if (attrValue === null) {
311
+ // Use valIfNull if defined
312
+ if (config.valIfNull !== undefined) {
313
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
314
+ if (mapsTo === '.') {
315
+ // Spread into root
316
+ if (typeof config.valIfNull === 'object' && config.valIfNull !== null) {
317
+ Object.assign(result, config.valIfNull);
318
+ }
319
+ }
320
+ else {
321
+ result[mapsTo] = config.valIfNull;
322
+ }
323
+ }
324
+ // Skip if no valIfNull and not Boolean (Boolean uses presence check)
325
+ else if (config.instanceOf !== 'Boolean') {
326
+ continue;
327
+ }
328
+ // For Boolean without valIfNull, fall through to parser
329
+ else {
330
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
331
+ const parsedValue = parser(attrValue);
332
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
333
+ result[mapsTo] = parsedValue;
334
+ }
193
335
  continue;
194
336
  }
195
- // Get parser
196
- const parser = config.parser || getDefaultParser(config.instanceOf);
197
- // Parse value
198
- const parsedValue = parser(attrValue);
337
+ // Attribute exists - parse normally
338
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
339
+ // Use cache if parseCache is specified
340
+ const parsedValue = config.parseCache
341
+ ? parseWithCache(attrValue, config, parser)
342
+ : parser(attrValue);
199
343
  // Determine target property
200
344
  const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
201
345
  // Add to result
package/parseWithAttrs.ts CHANGED
@@ -1,4 +1,148 @@
1
1
  import { AttrPatterns, AttrConfig } from './types';
2
+ import { globalParserRegistry } from './parserRegistry.js';
3
+
4
+ // Module-level cache for parsed attribute values
5
+ // Structure: Map<configKey, Map<attrValue, parsedValue>>
6
+ const parseCache = new Map<string, Map<string, any>>();
7
+
8
+ /**
9
+ * Resolves a parser specification to an actual parser function
10
+ * Supports:
11
+ * - Inline functions (direct use)
12
+ * - Named parsers from global registry
13
+ * - Custom element static methods (element-name.methodName)
14
+ *
15
+ * @param parserSpec - Parser function or string reference
16
+ * @returns The resolved parser function
17
+ * @throws Error if parser cannot be resolved
18
+ */
19
+ function resolveParser(parserSpec: ((v: string | null) => any) | string | undefined): ((v: string | null) => any) | undefined {
20
+ // Undefined - no parser specified
21
+ if (parserSpec === undefined) {
22
+ return undefined;
23
+ }
24
+
25
+ // Inline function - use directly
26
+ if (typeof parserSpec === 'function') {
27
+ return parserSpec;
28
+ }
29
+
30
+ // String reference - resolve it
31
+ if (typeof parserSpec === 'string') {
32
+ // Check if it's a custom element reference (contains dot)
33
+ if (parserSpec.includes('.')) {
34
+ const dotIndex = parserSpec.indexOf('.');
35
+ const elementName = parserSpec.substring(0, dotIndex);
36
+ const methodName = parserSpec.substring(dotIndex + 1);
37
+
38
+ // Try custom element lookup
39
+ if (typeof customElements !== 'undefined') {
40
+ try {
41
+ const ctr = customElements.get(elementName);
42
+ if (ctr && typeof (ctr as any)[methodName] === 'function') {
43
+ return (ctr as any)[methodName];
44
+ }
45
+ } catch (e) {
46
+ // customElements.get might throw, fall through to registry
47
+ }
48
+ }
49
+
50
+ // Fall through to global registry (allows dot notation in registry too)
51
+ }
52
+
53
+ // Try global registry
54
+ const parser = globalParserRegistry.get(parserSpec);
55
+ if (parser) {
56
+ return parser;
57
+ }
58
+
59
+ // Not found anywhere
60
+ throw new Error(
61
+ `Parser "${parserSpec}" not found. ` +
62
+ `Check that it's registered in globalParserRegistry or exists as a static method on the custom element.`
63
+ );
64
+ }
65
+
66
+ return undefined;
67
+ }
68
+
69
+ /**
70
+ * Creates a cache key from an AttrConfig
71
+ * Includes instanceOf and parser identifier to ensure correct cache hits
72
+ */
73
+ function getCacheKey(config: AttrConfig<any>): string {
74
+ const instanceOfStr = typeof config.instanceOf === 'function'
75
+ ? config.instanceOf.name
76
+ : (config.instanceOf || 'default');
77
+
78
+ // Include parser in cache key
79
+ let parserStr: string;
80
+ if (config.parser === undefined) {
81
+ parserStr = 'builtin';
82
+ } else if (typeof config.parser === 'string') {
83
+ parserStr = `named:${config.parser}`;
84
+ } else {
85
+ parserStr = 'custom';
86
+ }
87
+
88
+ return `${instanceOfStr}|${parserStr}`;
89
+ }
90
+
91
+ /**
92
+ * Gets a cached parsed value or parses and caches it
93
+ * @param attrValue - The attribute value to parse (or null)
94
+ * @param config - The attribute configuration
95
+ * @param parser - The parser function to use
96
+ * @returns The parsed value
97
+ */
98
+ function parseWithCache(
99
+ attrValue: string | null,
100
+ config: AttrConfig<any>,
101
+ parser: (v: string | null) => any
102
+ ): any {
103
+ // Skip caching for Boolean (presence check doesn't benefit from caching)
104
+ if (config.instanceOf === 'Boolean') {
105
+ return parser(attrValue);
106
+ }
107
+
108
+ // Get or create cache for this config
109
+ const cacheKey = getCacheKey(config);
110
+ if (!parseCache.has(cacheKey)) {
111
+ parseCache.set(cacheKey, new Map());
112
+ }
113
+
114
+ const valueCache = parseCache.get(cacheKey)!;
115
+
116
+ // Use special key for null values
117
+ const valueCacheKey = attrValue === null ? '__NULL__' : attrValue;
118
+
119
+ // Check if we have a cached value
120
+ if (valueCache.has(valueCacheKey)) {
121
+ const cachedValue = valueCache.get(valueCacheKey);
122
+
123
+ // Return clone if requested
124
+ if (config.parseCache === 'cloned') {
125
+ // Use structuredClone for deep cloning
126
+ return structuredClone(cachedValue);
127
+ }
128
+
129
+ // Return shared reference
130
+ return cachedValue;
131
+ }
132
+
133
+ // Parse the value
134
+ const parsedValue = parser(attrValue);
135
+
136
+ // Store in cache
137
+ valueCache.set(valueCacheKey, parsedValue);
138
+
139
+ // Return clone if requested (even on first parse)
140
+ if (config.parseCache === 'cloned') {
141
+ return structuredClone(parsedValue);
142
+ }
143
+
144
+ return parsedValue;
145
+ }
2
146
 
3
147
  /**
4
148
  * Checks if a string contains a dash or non-ASCII character
@@ -221,16 +365,42 @@ export function parseWithAttrs<T = any>(
221
365
  for (const [key, { attrName, config }] of resolvedAttrs) {
222
366
  const attrValue = getAttributeValue(element, attrName, allowUnprefixed);
223
367
 
224
- // Skip if attribute doesn't exist
225
- if (attrValue === null && config.instanceOf !== 'Boolean') {
368
+ // Handle missing attribute
369
+ if (attrValue === null) {
370
+ // Use valIfNull if defined
371
+ if (config.valIfNull !== undefined) {
372
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
373
+
374
+ if (mapsTo === '.') {
375
+ // Spread into root
376
+ if (typeof config.valIfNull === 'object' && config.valIfNull !== null) {
377
+ Object.assign(result, config.valIfNull);
378
+ }
379
+ } else {
380
+ result[mapsTo as string] = config.valIfNull;
381
+ }
382
+ }
383
+ // Skip if no valIfNull and not Boolean (Boolean uses presence check)
384
+ else if (config.instanceOf !== 'Boolean') {
385
+ continue;
386
+ }
387
+ // For Boolean without valIfNull, fall through to parser
388
+ else {
389
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
390
+ const parsedValue = parser(attrValue);
391
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
392
+ result[mapsTo as string] = parsedValue;
393
+ }
226
394
  continue;
227
395
  }
228
396
 
229
- // Get parser
230
- const parser = config.parser || getDefaultParser(config.instanceOf);
397
+ // Attribute exists - parse normally
398
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
231
399
 
232
- // Parse value
233
- const parsedValue = parser(attrValue);
400
+ // Use cache if parseCache is specified
401
+ const parsedValue = config.parseCache
402
+ ? parseWithCache(attrValue, config, parser)
403
+ : parser(attrValue);
234
404
 
235
405
  // Determine target property
236
406
  const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);