assign-gingerly 0.0.18 → 0.0.19

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 CHANGED
@@ -292,7 +292,7 @@ This guarantees that applying the reversal object restores the object to its exa
292
292
  ## Dependency injection based on a registry object and a Symbolic reference mapping
293
293
 
294
294
  ```Typescript
295
- interface IBaseRegistryItem<T = any, TObjToExtend = any> {
295
+ interface IEnhancementRegistryItem<T = any, TObjToExtend = any> {
296
296
  spawn: {new(objToExtend: TObjToExtend, ctx: SpawnContext, initVals: Partial<T>): T}
297
297
  symlinks?: {[key: symbol]: keyof T}
298
298
  // Optional: for element enhancement access
@@ -317,15 +317,15 @@ class YourEnhancement{
317
317
  set madAboutFourteen(nv){}
318
318
  }
319
319
 
320
- class BaseRegistry{
321
- push(IBaseRegistryItem | IBaseRegistryItem[]){
320
+ class EnhancementRegistry{
321
+ push(IEnhancementRegistryItem | IEnhancementRegistryItem[]){
322
322
  ...
323
323
  }
324
324
  }
325
325
 
326
326
  //Here's where the dependency injection mapping takes place
327
- const baseRegistry = new BaseRegistry;
328
- baseRegistry.push([
327
+ const EnhancementRegistry = new EnhancementRegistry;
328
+ EnhancementRegistry.push([
329
329
  {
330
330
  symlinks: {
331
331
  [isHappy]: 'isHappy'
@@ -347,7 +347,7 @@ const result = assignGingerly({}, {
347
347
  '?.style?.height': '40px',
348
348
  '?.enh?.mellowYellow?.madAboutFourteen': true
349
349
  }, {
350
- registry: BaseRegistry
350
+ registry: EnhancementRegistry
351
351
  });
352
352
  //result.set[isMellow] = false;
353
353
  ```
@@ -401,7 +401,7 @@ const registryItem = {
401
401
  enhKey: 'myEnh'
402
402
  };
403
403
 
404
- const registry = new BaseRegistry();
404
+ const registry = new EnhancementRegistry();
405
405
  registry.push(registryItem);
406
406
 
407
407
  const element = document.createElement('div');
@@ -438,7 +438,7 @@ const registryItem = {
438
438
  }
439
439
  };
440
440
 
441
- const registry = new BaseRegistry();
441
+ const registry = new EnhancementRegistry();
442
442
  registry.push(registryItem);
443
443
 
444
444
  const target = {};
@@ -465,7 +465,7 @@ const result = assignGingerly({}, {
465
465
  '?.style.height': '40px',
466
466
  '?.enh?.mellowYellow?.madAboutFourteen': true
467
467
  }, {
468
- registry: BaseRegistry
468
+ registry: EnhancementRegistry
469
469
  });
470
470
  ```
471
471
  </details>
@@ -551,7 +551,7 @@ myElement.assignGingerly({
551
551
  <details>
552
552
  <summary>Lazy Registry Creation</summary>
553
553
 
554
- Each `CustomElementRegistry` instance gets its own `enhancementRegistry` property via a lazy getter. The `BaseRegistry` instance is created on first access and cached for subsequent uses:
554
+ Each `CustomElementRegistry` instance gets its own `enhancementRegistry` property via a lazy getter. The `EnhancementRegistry` instance is created on first access and cached for subsequent uses:
555
555
 
556
556
  ```TypeScript
557
557
  const element1 = document.createElement('div');
@@ -572,7 +572,7 @@ console.log(registry1 === element1.customElementRegistry.enhancementRegistry); /
572
572
  You can still provide an explicit `registry` option to override the automatic behavior:
573
573
 
574
574
  ```TypeScript
575
- const customRegistry = new BaseRegistry();
575
+ const customRegistry = new EnhancementRegistry();
576
576
  // ... configure customRegistry ...
577
577
 
578
578
  myElement.assignGingerly({
@@ -593,7 +593,7 @@ The `enh.set` proxy allows you to assign properties to enhancements using a clea
593
593
 
594
594
  ```TypeScript
595
595
  import 'assign-gingerly/object-extension.js';
596
- //import { BaseRegistry } from 'assign-gingerly';
596
+ //import { EnhancementRegistry } from 'assign-gingerly';
597
597
 
598
598
  // Define an enhancement class
599
599
  class MyEnhancement {
@@ -662,7 +662,7 @@ Element enhancement classes should follow this constructor signature:
662
662
 
663
663
  ```TypeScript
664
664
  interface SpawnContext<T, TMountContext = any> {
665
- config: IBaseRegistryItem<T>;
665
+ config: IEnhancementRegistryItem<T>;
666
666
  mountCtx?: TMountContext; // Optional custom context passed by caller
667
667
  }
668
668
 
@@ -716,7 +716,7 @@ This is useful for:
716
716
  In addition to spawn and symlinks, registry items support optional properties `enhKey`, `withAttrs`, `canSpawn`, and `lifecycleKeys`:
717
717
 
718
718
  ```TypeScript
719
- interface IBaseRegistryItem<T, TObj = Element> {
719
+ interface IEnhancementRegistryItem<T, TObj = Element> {
720
720
  spawn: {
721
721
  new (obj?: TObj, ctx?: SpawnContext<T>, initVals?: Partial<T>): T;
722
722
  canSpawn?: (obj: TObj, ctx?: SpawnContext<T>) => boolean; // Optional spawn guard
@@ -813,10 +813,10 @@ console.log(element.enh.plainData); // { prop1: 'value1', prop2: 'value2' }
813
813
  <details>
814
814
  <summary>Finding Registry Items by enhKey</summary>
815
815
 
816
- The `BaseRegistry` class includes a `findByEnhKey` method:
816
+ The `EnhancementRegistry` class includes a `findByEnhKey` method:
817
817
 
818
818
  ```TypeScript
819
- const registry = new BaseRegistry();
819
+ const registry = new EnhancementRegistry();
820
820
  registry.push({
821
821
  spawn: MyEnhancement,
822
822
  enhKey: 'myEnh'
@@ -1228,7 +1228,7 @@ class DivOnlyEnhancement {
1228
1228
  }
1229
1229
  }
1230
1230
 
1231
- const registry = new BaseRegistry();
1231
+ const registry = new EnhancementRegistry();
1232
1232
  registry.push({
1233
1233
  spawn: DivOnlyEnhancement,
1234
1234
  enhKey: 'divOnly'
@@ -1262,7 +1262,7 @@ static canSpawn(obj: any, ctx?: SpawnContext<T>): boolean
1262
1262
  ```
1263
1263
 
1264
1264
  - `obj`: The target object being enhanced (element, plain object, etc.)
1265
- - `ctx`: Optional spawn context containing `{ config: IBaseRegistryItem<T> }`
1265
+ - `ctx`: Optional spawn context containing `{ config: IEnhancementRegistryItem<T> }`
1266
1266
  - Returns: `true` to allow spawning, `false` to block
1267
1267
 
1268
1268
  ### Use Cases
@@ -1329,7 +1329,7 @@ class ValidatedEnhancement {
1329
1329
  ### Example with Dependency Injection
1330
1330
 
1331
1331
  ```TypeScript
1332
- import assignGingerly, { BaseRegistry } from 'assign-gingerly';
1332
+ import assignGingerly, { EnhancementRegistry } from 'assign-gingerly';
1333
1333
 
1334
1334
  class ElementOnlyEnhancement {
1335
1335
  value = null;
@@ -1339,7 +1339,7 @@ class ElementOnlyEnhancement {
1339
1339
  }
1340
1340
  }
1341
1341
 
1342
- const registry = new BaseRegistry();
1342
+ const registry = new EnhancementRegistry();
1343
1343
  const enhSymbol = Symbol.for('myEnhancement');
1344
1344
 
1345
1345
  registry.push({
package/assignGingerly.js CHANGED
@@ -1,3 +1,21 @@
1
+ // Polyfill for Map.prototype.getOrInsert and WeakMap.prototype.getOrInsert
2
+ if (typeof Map.prototype.getOrInsert !== 'function') {
3
+ Map.prototype.getOrInsert = function(key, insert) {
4
+ if (this.has(key)) return this.get(key);
5
+ const value = insert();
6
+ this.set(key, value);
7
+ return value;
8
+ };
9
+ }
10
+ if (typeof WeakMap.prototype.getOrInsert !== 'function') {
11
+ WeakMap.prototype.getOrInsert = function(key, insert) {
12
+ if (this.has(key)) return this.get(key);
13
+ const value = insert();
14
+ this.set(key, value);
15
+ return value;
16
+ };
17
+ }
18
+
1
19
  /**
2
20
  * GUID for global instance map storage to ensure uniqueness across package versions
3
21
  */
@@ -16,7 +34,7 @@ export function getInstanceMap() {
16
34
  /**
17
35
  * Base registry class for managing enhancement configurations
18
36
  */
19
- export class BaseRegistry {
37
+ export class EnhancementRegistry {
20
38
  items = [];
21
39
  push(items) {
22
40
  if (Array.isArray(items)) {
@@ -142,7 +160,7 @@ export function assignGingerly(target, source, options) {
142
160
  if (!target || typeof target !== 'object') {
143
161
  return target;
144
162
  }
145
- const registry = options?.registry instanceof BaseRegistry
163
+ const registry = options?.registry instanceof EnhancementRegistry
146
164
  ? options.registry
147
165
  : options?.registry
148
166
  ? new options.registry()
@@ -305,10 +323,7 @@ export function assignGingerly(target, source, options) {
305
323
  if (registryItem) {
306
324
  const instanceMap = getInstanceMap();
307
325
  // Get or initialize the instances map for this target
308
- if (!instanceMap.has(target)) {
309
- instanceMap.set(target, new Map());
310
- }
311
- const instances = instanceMap.get(target);
326
+ const instances = instanceMap.getOrInsert(target, () => new Map());
312
327
  // Check if instance already exists (keyed by registryItem)
313
328
  let instance = instances.get(registryItem);
314
329
  if (!instance) {
@@ -363,10 +378,7 @@ export function assignGingerly(target, source, options) {
363
378
  const registryItem = registry.findBySymbol(prop);
364
379
  if (registryItem) {
365
380
  const instanceMap = getInstanceMap();
366
- if (!instanceMap.has(target)) {
367
- instanceMap.set(target, new Map());
368
- }
369
- const instances = instanceMap.get(target);
381
+ const instances = instanceMap.getOrInsert(target, () => new Map());
370
382
  let instance = instances.get(registryItem);
371
383
  if (!instance) {
372
384
  const SpawnClass = registryItem.spawn;
package/assignGingerly.ts CHANGED
@@ -11,7 +11,7 @@ export type IBaseRegistryItem<T = any> = EnhancementConfig<T>;
11
11
  * Interface for the options passed to assignGingerly
12
12
  */
13
13
  export interface IAssignGingerlyOptions {
14
- registry?: typeof BaseRegistry | BaseRegistry;
14
+ registry?: typeof EnhancementRegistry | EnhancementRegistry;
15
15
  }
16
16
 
17
17
  /**
@@ -34,7 +34,7 @@ export function getInstanceMap(): WeakMap<object, Map<EnhancementConfig, any>> {
34
34
  /**
35
35
  * Base registry class for managing enhancement configurations
36
36
  */
37
- export class BaseRegistry {
37
+ export class EnhancementRegistry {
38
38
  private items: EnhancementConfig[] = [];
39
39
 
40
40
  push(items: EnhancementConfig | EnhancementConfig[]): void {
@@ -179,7 +179,7 @@ export function assignGingerly(
179
179
  return target;
180
180
  }
181
181
 
182
- const registry = options?.registry instanceof BaseRegistry
182
+ const registry = options?.registry instanceof EnhancementRegistry
183
183
  ? options.registry
184
184
  : options?.registry
185
185
  ? new options.registry()
@@ -350,10 +350,7 @@ export function assignGingerly(
350
350
  if (registryItem) {
351
351
  const instanceMap = getInstanceMap();
352
352
  // Get or initialize the instances map for this target
353
- if (!instanceMap.has(target)) {
354
- instanceMap.set(target, new Map());
355
- }
356
- const instances = instanceMap.get(target)!;
353
+ const instances = instanceMap.getOrInsert(target, () => new Map());
357
354
 
358
355
  // Check if instance already exists (keyed by registryItem)
359
356
  let instance = instances.get(registryItem);
@@ -418,10 +415,7 @@ export function assignGingerly(
418
415
  const registryItem = registry.findBySymbol(prop);
419
416
  if (registryItem) {
420
417
  const instanceMap = getInstanceMap();
421
- if (!instanceMap.has(target)) {
422
- instanceMap.set(target, new Map());
423
- }
424
- const instances = instanceMap.get(target)!;
418
+ const instances = instanceMap.getOrInsert(target, () => new Map());
425
419
  let instance = instances.get(registryItem);
426
420
 
427
421
  if (!instance) {
package/buildCSSQuery.js CHANGED
@@ -1,44 +1,11 @@
1
- /**
2
- * Resolves template variables in a string recursively
3
- * @param template - Template string with ${var} placeholders
4
- * @param patterns - The patterns object containing variable values
5
- * @param resolvedCache - Cache of already resolved values
6
- * @param visitedKeys - Set of keys being resolved (for cycle detection)
7
- * @returns Resolved string
8
- */
9
- function resolveTemplate(template, patterns, resolvedCache, visitedKeys = new Set()) {
10
- return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
11
- // Check if already resolved
12
- if (resolvedCache.has(varName)) {
13
- return resolvedCache.get(varName);
14
- }
15
- // Check for circular reference
16
- if (visitedKeys.has(varName)) {
17
- throw new Error(`Circular reference detected in template variable: ${varName}`);
18
- }
19
- const value = patterns[varName];
20
- if (value === undefined) {
21
- throw new Error(`Undefined template variable: ${varName}`);
22
- }
23
- if (typeof value === 'string') {
24
- // Recursively resolve
25
- visitedKeys.add(varName);
26
- const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
27
- visitedKeys.delete(varName);
28
- resolvedCache.set(varName, resolved);
29
- return resolved;
30
- }
31
- // Non-string value, return as-is
32
- return String(value);
33
- });
34
- }
1
+ import { resolveTemplate } from './resolveTemplate.js';
35
2
  /**
36
3
  * Extracts attribute names from withAttrs configuration
37
4
  * Resolves template variables and excludes underscore-prefixed config keys
38
5
  * @param withAttrs - The attribute patterns configuration
39
6
  * @returns Array of resolved attribute names
40
7
  */
41
- function extractAttributeNames(withAttrs) {
8
+ export function extractAttributeNames(withAttrs) {
42
9
  const names = [];
43
10
  const resolvedCache = new Map();
44
11
  // Add base if present
package/buildCSSQuery.ts CHANGED
@@ -1,49 +1,5 @@
1
1
  import { EnhancementConfig, AttrPatterns } from './types/assign-gingerly/types';
2
-
3
- /**
4
- * Resolves template variables in a string recursively
5
- * @param template - Template string with ${var} placeholders
6
- * @param patterns - The patterns object containing variable values
7
- * @param resolvedCache - Cache of already resolved values
8
- * @param visitedKeys - Set of keys being resolved (for cycle detection)
9
- * @returns Resolved string
10
- */
11
- function resolveTemplate(
12
- template: string,
13
- patterns: Record<string, any>,
14
- resolvedCache: Map<string, string>,
15
- visitedKeys: Set<string> = new Set()
16
- ): string {
17
- return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
18
- // Check if already resolved
19
- if (resolvedCache.has(varName)) {
20
- return resolvedCache.get(varName)!;
21
- }
22
-
23
- // Check for circular reference
24
- if (visitedKeys.has(varName)) {
25
- throw new Error(`Circular reference detected in template variable: ${varName}`);
26
- }
27
-
28
- const value = patterns[varName];
29
-
30
- if (value === undefined) {
31
- throw new Error(`Undefined template variable: ${varName}`);
32
- }
33
-
34
- if (typeof value === 'string') {
35
- // Recursively resolve
36
- visitedKeys.add(varName);
37
- const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
38
- visitedKeys.delete(varName);
39
- resolvedCache.set(varName, resolved);
40
- return resolved;
41
- }
42
-
43
- // Non-string value, return as-is
44
- return String(value);
45
- });
46
- }
2
+ import { resolveTemplate } from './resolveTemplate.js';
47
3
 
48
4
  /**
49
5
  * Extracts attribute names from withAttrs configuration
@@ -51,7 +7,7 @@ function resolveTemplate(
51
7
  * @param withAttrs - The attribute patterns configuration
52
8
  * @returns Array of resolved attribute names
53
9
  */
54
- function extractAttributeNames(withAttrs: AttrPatterns<any>): string[] {
10
+ export function extractAttributeNames(withAttrs: AttrPatterns<any>): string[] {
55
11
  const names: string[] = [];
56
12
  const resolvedCache = new Map<string, string>();
57
13
 
package/global.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ // Type declarations for Map.prototype.getOrInsert and WeakMap.prototype.getOrInsert
2
+ // Feature is now supported in all modern browsers (Chrome 146+, Firefox 134+, Safari 18.2+)
3
+ // See: https://web-platform-dx.github.io/web-features-explorer/features/getorinsert/
4
+
5
+ interface Map<K, V> {
6
+ /**
7
+ * Returns the value associated with the key if it exists, otherwise inserts
8
+ * the value returned by the insert callback and returns it.
9
+ * @param key The key to look up
10
+ * @param insert A callback that returns the value to insert if the key doesn't exist
11
+ * @returns The existing or newly inserted value
12
+ */
13
+ getOrInsert(key: K, insert: () => V): V;
14
+ }
15
+
16
+ interface WeakMap<K extends object, V> {
17
+ /**
18
+ * Returns the value associated with the key if it exists, otherwise inserts
19
+ * the value returned by the insert callback and returns it.
20
+ * @param key The key to look up
21
+ * @param insert A callback that returns the value to insert if the key doesn't exist
22
+ * @returns The existing or newly inserted value
23
+ */
24
+ getOrInsert(key: K, insert: () => V): V;
25
+ }
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  export { assignGingerly } from './assignGingerly.js';
2
2
  export { assignTentatively } from './assignTentatively.js';
3
- export { BaseRegistry } from './assignGingerly.js';
3
+ export { EnhancementRegistry } from './assignGingerly.js';
4
4
  export { waitForEvent } from './waitForEvent.js';
5
5
  export { ParserRegistry, globalParserRegistry } from './parserRegistry.js';
6
6
  export { parseWithAttrs } from './parseWithAttrs.js';
7
7
  export { buildCSSQuery } from './buildCSSQuery.js';
8
+ export { resolveTemplate } from './resolveTemplate.js';
8
9
  import './object-extension.js';
package/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export {assignGingerly} from './assignGingerly.js';
2
2
  export {assignTentatively} from './assignTentatively.js';
3
- export {BaseRegistry} from './assignGingerly.js';
3
+ export {EnhancementRegistry} from './assignGingerly.js';
4
4
  export {waitForEvent} from './waitForEvent.js';
5
5
  export {ParserRegistry, globalParserRegistry} from './parserRegistry.js';
6
6
  export {parseWithAttrs} from './parseWithAttrs.js';
7
7
  export {buildCSSQuery} from './buildCSSQuery.js';
8
+ export {resolveTemplate} from './resolveTemplate.js';
8
9
  import './object-extension.js';
@@ -1,4 +1,22 @@
1
- import assignGingerly, { BaseRegistry, getInstanceMap } from './assignGingerly.js';
1
+ // Polyfill for Map.prototype.getOrInsert and WeakMap.prototype.getOrInsert
2
+ if (typeof Map.prototype.getOrInsert !== 'function') {
3
+ Map.prototype.getOrInsert = function(key, insert) {
4
+ if (this.has(key)) return this.get(key);
5
+ const value = insert();
6
+ this.set(key, value);
7
+ return value;
8
+ };
9
+ }
10
+ if (typeof WeakMap.prototype.getOrInsert !== 'function') {
11
+ WeakMap.prototype.getOrInsert = function(key, insert) {
12
+ if (this.has(key)) return this.get(key);
13
+ const value = insert();
14
+ this.set(key, value);
15
+ return value;
16
+ };
17
+ }
18
+
19
+ import assignGingerly, { EnhancementRegistry, getInstanceMap } from './assignGingerly.js';
2
20
  import { parseWithAttrs } from './parseWithAttrs.js';
3
21
  /**
4
22
  * Normalizes lifecycleKeys to always return an object with dispose and resolved keys
@@ -21,7 +39,7 @@ if (typeof CustomElementRegistry !== 'undefined') {
21
39
  Object.defineProperty(CustomElementRegistry.prototype, 'enhancementRegistry', {
22
40
  get: function () {
23
41
  // Create a new BaseRegistry instance on first access and cache it
24
- const registry = new BaseRegistry();
42
+ const registry = new EnhancementRegistry();
25
43
  // Replace the getter with the actual value
26
44
  Object.defineProperty(this, 'enhancementRegistry', {
27
45
  value: registry,
@@ -66,10 +84,7 @@ class ElementEnhancementContainer {
66
84
  }
67
85
  // Get or create instance using the global instance map
68
86
  const instanceMap = getInstanceMap();
69
- if (!instanceMap.has(element)) {
70
- instanceMap.set(element, new Map());
71
- }
72
- const instances = instanceMap.get(element);
87
+ const instances = instanceMap.getOrInsert(element, () => new Map());
73
88
  let instance = instances.get(registryItem);
74
89
  if (!instance) {
75
90
  // Need to spawn
@@ -201,10 +216,7 @@ class ElementEnhancementContainer {
201
216
  const SpawnClass = registryItem.spawn;
202
217
  // Check the global instance map first
203
218
  const instanceMap = getInstanceMap();
204
- if (!instanceMap.has(element)) {
205
- instanceMap.set(element, new Map());
206
- }
207
- const instances = instanceMap.get(element);
219
+ const instances = instanceMap.getOrInsert(element, () => new Map());
208
220
  let instance = instances.get(registryItem);
209
221
  if (!instance) {
210
222
  // Need to spawn
@@ -259,10 +271,7 @@ if (typeof Element !== 'undefined') {
259
271
  const enhContainerWeakMap = new WeakMap();
260
272
  Object.defineProperty(Element.prototype, 'enh', {
261
273
  get: function () {
262
- if (!enhContainerWeakMap.has(this)) {
263
- enhContainerWeakMap.set(this, new ElementEnhancementContainer(this));
264
- }
265
- return enhContainerWeakMap.get(this);
274
+ return enhContainerWeakMap.getOrInsert(this, () => new ElementEnhancementContainer(this));
266
275
  },
267
276
  enumerable: true,
268
277
  configurable: true,
@@ -1,4 +1,4 @@
1
- import assignGingerly, { BaseRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
1
+ import assignGingerly, { EnhancementRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
2
2
  import { parseWithAttrs } from './parseWithAttrs.js';
3
3
 
4
4
  /**
@@ -21,7 +21,7 @@ function normalizeLifecycleKeys(lifecycleKeys: true | { dispose?: string | symbo
21
21
  */
22
22
  declare global {
23
23
  interface CustomElementRegistry {
24
- enhancementRegistry: typeof BaseRegistry | BaseRegistry;
24
+ enhancementRegistry: typeof EnhancementRegistry | EnhancementRegistry;
25
25
  }
26
26
 
27
27
  interface Element {
@@ -84,7 +84,7 @@ if (typeof CustomElementRegistry !== 'undefined') {
84
84
  Object.defineProperty(CustomElementRegistry.prototype, 'enhancementRegistry', {
85
85
  get: function () {
86
86
  // Create a new BaseRegistry instance on first access and cache it
87
- const registry = new BaseRegistry();
87
+ const registry = new EnhancementRegistry();
88
88
  // Replace the getter with the actual value
89
89
  Object.defineProperty(this, 'enhancementRegistry', {
90
90
  value: registry,
@@ -136,10 +136,7 @@ class ElementEnhancementContainer {
136
136
 
137
137
  // Get or create instance using the global instance map
138
138
  const instanceMap = getInstanceMap();
139
- if (!instanceMap.has(element)) {
140
- instanceMap.set(element, new Map());
141
- }
142
- const instances = instanceMap.get(element)!;
139
+ const instances = instanceMap.getOrInsert(element, () => new Map());
143
140
 
144
141
  let instance = instances.get(registryItem);
145
142
 
@@ -304,10 +301,7 @@ class ElementEnhancementContainer {
304
301
 
305
302
  // Check the global instance map first
306
303
  const instanceMap = getInstanceMap();
307
- if (!instanceMap.has(element)) {
308
- instanceMap.set(element, new Map());
309
- }
310
- const instances = instanceMap.get(element)!;
304
+ const instances = instanceMap.getOrInsert(element, () => new Map());
311
305
 
312
306
  let instance = instances.get(registryItem);
313
307
 
@@ -376,11 +370,7 @@ if (typeof Element !== 'undefined') {
376
370
 
377
371
  Object.defineProperty(Element.prototype, 'enh', {
378
372
  get: function (this: Element) {
379
- if (!enhContainerWeakMap.has(this)) {
380
- enhContainerWeakMap.set(this, new ElementEnhancementContainer(this));
381
- }
382
-
383
- return enhContainerWeakMap.get(this);
373
+ return enhContainerWeakMap.getOrInsert(this, () => new ElementEnhancementContainer(this));
384
374
  },
385
375
  enumerable: true,
386
376
  configurable: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
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": {
@@ -47,6 +47,10 @@
47
47
  "./buildCSSQuery.js": {
48
48
  "default": "./buildCSSQuery.js",
49
49
  "types": "./buildCSSQuery.ts"
50
+ },
51
+ "./resolveTemplate.js": {
52
+ "default": "./resolveTemplate.js",
53
+ "types": "./resolveTemplate.ts"
50
54
  }
51
55
  },
52
56
  "main": "index.js",
package/parseWithAttrs.js CHANGED
@@ -1,4 +1,23 @@
1
+ // Polyfill for Map.prototype.getOrInsert and WeakMap.prototype.getOrInsert
2
+ if (typeof Map.prototype.getOrInsert !== 'function') {
3
+ Map.prototype.getOrInsert = function(key, insert) {
4
+ if (this.has(key)) return this.get(key);
5
+ const value = insert();
6
+ this.set(key, value);
7
+ return value;
8
+ };
9
+ }
10
+ if (typeof WeakMap.prototype.getOrInsert !== 'function') {
11
+ WeakMap.prototype.getOrInsert = function(key, insert) {
12
+ if (this.has(key)) return this.get(key);
13
+ const value = insert();
14
+ this.set(key, value);
15
+ return value;
16
+ };
17
+ }
18
+
1
19
  import { globalParserRegistry } from './parserRegistry.js';
20
+ import { resolveTemplate } from './resolveTemplate.js';
2
21
  // Module-level cache for parsed attribute values
3
22
  // Structure: Map<configKey, Map<attrValue, parsedValue>>
4
23
  const parseCache = new Map();
@@ -89,10 +108,7 @@ function parseWithCache(attrValue, config, parser) {
89
108
  }
90
109
  // Get or create cache for this config
91
110
  const cacheKey = getCacheKey(config);
92
- if (!parseCache.has(cacheKey)) {
93
- parseCache.set(cacheKey, new Map());
94
- }
95
- const valueCache = parseCache.get(cacheKey);
111
+ const valueCache = parseCache.getOrInsert(cacheKey, () => new Map());
96
112
  // Use special key for null values
97
113
  const valueCacheKey = attrValue === null ? '__NULL__' : attrValue;
98
114
  // Check if we have a cached value
@@ -160,40 +176,6 @@ function getAttributeValue(element, attrName, allowUnprefixed) {
160
176
  return enhValue;
161
177
  return element.getAttribute(attrName);
162
178
  }
163
- /**
164
- * Resolves template variables in a string recursively
165
- * @param template - Template string with ${var} placeholders
166
- * @param patterns - The patterns object containing variable values
167
- * @param resolvedCache - Cache of already resolved values
168
- * @param visitedKeys - Set of keys being resolved (for cycle detection)
169
- * @returns Resolved string
170
- */
171
- function resolveTemplate(template, patterns, resolvedCache, visitedKeys = new Set()) {
172
- return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
173
- // Check if already resolved
174
- if (resolvedCache.has(varName)) {
175
- return resolvedCache.get(varName);
176
- }
177
- // Check for circular reference
178
- if (visitedKeys.has(varName)) {
179
- throw new Error(`Circular reference detected in template variable: ${varName}`);
180
- }
181
- const value = patterns[varName];
182
- if (value === undefined) {
183
- throw new Error(`Undefined template variable: ${varName}`);
184
- }
185
- if (typeof value === 'string') {
186
- // Recursively resolve
187
- visitedKeys.add(varName);
188
- const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
189
- visitedKeys.delete(varName);
190
- resolvedCache.set(varName, resolved);
191
- return resolved;
192
- }
193
- // Non-string value, return as-is
194
- return String(value);
195
- });
196
- }
197
179
  /**
198
180
  * Gets the default parser for a given instanceOf type
199
181
  */
package/parseWithAttrs.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AttrPatterns, AttrConfig } from './types/assign-gingerly/types';
2
2
  import { globalParserRegistry } from './parserRegistry.js';
3
+ import { resolveTemplate } from './resolveTemplate.js';
3
4
 
4
5
  // Module-level cache for parsed attribute values
5
6
  // Structure: Map<configKey, Map<attrValue, parsedValue>>
@@ -107,11 +108,7 @@ function parseWithCache(
107
108
 
108
109
  // Get or create cache for this config
109
110
  const cacheKey = getCacheKey(config);
110
- if (!parseCache.has(cacheKey)) {
111
- parseCache.set(cacheKey, new Map());
112
- }
113
-
114
- const valueCache = parseCache.get(cacheKey)!;
111
+ const valueCache = parseCache.getOrInsert(cacheKey, () => new Map());
115
112
 
116
113
  // Use special key for null values
117
114
  const valueCacheKey = attrValue === null ? '__NULL__' : attrValue;
@@ -193,51 +190,6 @@ function getAttributeValue(
193
190
  return element.getAttribute(attrName);
194
191
  }
195
192
 
196
- /**
197
- * Resolves template variables in a string recursively
198
- * @param template - Template string with ${var} placeholders
199
- * @param patterns - The patterns object containing variable values
200
- * @param resolvedCache - Cache of already resolved values
201
- * @param visitedKeys - Set of keys being resolved (for cycle detection)
202
- * @returns Resolved string
203
- */
204
- function resolveTemplate(
205
- template: string,
206
- patterns: Record<string, any>,
207
- resolvedCache: Map<string, string>,
208
- visitedKeys: Set<string> = new Set()
209
- ): string {
210
- return template.replace(/\$\{(\w+)\}/g, (match, varName) => {
211
- // Check if already resolved
212
- if (resolvedCache.has(varName)) {
213
- return resolvedCache.get(varName)!;
214
- }
215
-
216
- // Check for circular reference
217
- if (visitedKeys.has(varName)) {
218
- throw new Error(`Circular reference detected in template variable: ${varName}`);
219
- }
220
-
221
- const value = patterns[varName];
222
-
223
- if (value === undefined) {
224
- throw new Error(`Undefined template variable: ${varName}`);
225
- }
226
-
227
- if (typeof value === 'string') {
228
- // Recursively resolve
229
- visitedKeys.add(varName);
230
- const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
231
- visitedKeys.delete(varName);
232
- resolvedCache.set(varName, resolved);
233
- return resolved;
234
- }
235
-
236
- // Non-string value, return as-is
237
- return String(value);
238
- });
239
- }
240
-
241
193
  /**
242
194
  * Gets the default parser for a given instanceOf type
243
195
  */
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Resolves template variables in a string recursively
3
+ * Supports ${varName} syntax for variable substitution
4
+ *
5
+ * @param template - Template string with ${var} placeholders
6
+ * @param patterns - The patterns object containing variable values
7
+ * @param resolvedCache - Cache of already resolved values
8
+ * @param visitedKeys - Set of keys being resolved (for cycle detection)
9
+ * @returns Resolved string
10
+ *
11
+ * @throws Error if circular reference is detected
12
+ * @throws Error if template variable is undefined
13
+ *
14
+ * @example
15
+ * const patterns = {
16
+ * base: 'my-component',
17
+ * theme: '${base}-theme'
18
+ * };
19
+ * const cache = new Map();
20
+ *
21
+ * resolveTemplate('${theme}', patterns, cache);
22
+ * // Returns: 'my-component-theme'
23
+ */
24
+ export function resolveTemplate(template, patterns, resolvedCache, visitedKeys = new Set()) {
25
+ return template.replace(/\$\{(\w+)\}/g, (_match, varName) => {
26
+ // Check if already resolved
27
+ if (resolvedCache.has(varName)) {
28
+ return resolvedCache.get(varName);
29
+ }
30
+ // Check for circular reference
31
+ if (visitedKeys.has(varName)) {
32
+ throw new Error(`Circular reference detected in template variable: ${varName}`);
33
+ }
34
+ const value = patterns[varName];
35
+ if (value === undefined) {
36
+ throw new Error(`Undefined template variable: ${varName}`);
37
+ }
38
+ if (typeof value === 'string') {
39
+ // Recursively resolve
40
+ visitedKeys.add(varName);
41
+ const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
42
+ visitedKeys.delete(varName);
43
+ resolvedCache.set(varName, resolved);
44
+ return resolved;
45
+ }
46
+ // Non-string value, return as-is
47
+ return String(value);
48
+ });
49
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Resolves template variables in a string recursively
3
+ * Supports ${varName} syntax for variable substitution
4
+ *
5
+ * @param template - Template string with ${var} placeholders
6
+ * @param patterns - The patterns object containing variable values
7
+ * @param resolvedCache - Cache of already resolved values
8
+ * @param visitedKeys - Set of keys being resolved (for cycle detection)
9
+ * @returns Resolved string
10
+ *
11
+ * @throws Error if circular reference is detected
12
+ * @throws Error if template variable is undefined
13
+ *
14
+ * @example
15
+ * const patterns = {
16
+ * base: 'my-component',
17
+ * theme: '${base}-theme'
18
+ * };
19
+ * const cache = new Map();
20
+ *
21
+ * resolveTemplate('${theme}', patterns, cache);
22
+ * // Returns: 'my-component-theme'
23
+ */
24
+ export function resolveTemplate(
25
+ template: string,
26
+ patterns: Record<string, any>,
27
+ resolvedCache: Map<string, string>,
28
+ visitedKeys: Set<string> = new Set()
29
+ ): string {
30
+ return template.replace(/\$\{(\w+)\}/g, (_match, varName) => {
31
+ // Check if already resolved
32
+ if (resolvedCache.has(varName)) {
33
+ return resolvedCache.get(varName)!;
34
+ }
35
+
36
+ // Check for circular reference
37
+ if (visitedKeys.has(varName)) {
38
+ throw new Error(`Circular reference detected in template variable: ${varName}`);
39
+ }
40
+
41
+ const value = patterns[varName];
42
+
43
+ if (value === undefined) {
44
+ throw new Error(`Undefined template variable: ${varName}`);
45
+ }
46
+
47
+ if (typeof value === 'string') {
48
+ // Recursively resolve
49
+ visitedKeys.add(varName);
50
+ const resolved = resolveTemplate(value, patterns, resolvedCache, visitedKeys);
51
+ visitedKeys.delete(varName);
52
+ resolvedCache.set(varName, resolved);
53
+ return resolved;
54
+ }
55
+
56
+ // Non-string value, return as-is
57
+ return String(value);
58
+ });
59
+ }