Source: lib/matching-component-manager.js

/**
 * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

// Any unique, deterministic stringify function would work.
// TODO: Benchmark against native JSON.stringify result of sort-keys deep
// https://github.com/sindresorhus/getKey-obj/blob/master/index.js#L17
// https://github.com/epoberezkin/fast-json-stable-stringify#benchmark
import stringify from 'fast-json-stable-stringify';

import ComponentManager from './component-manager.js';

const keyToNameSymbol = Symbol('keyToName');

/** Adds values to defined components by first attempting to match an
 * existing value in the component.
 */
export default class MatchingComponentManager extends ComponentManager {
  constructor(component) {
    super(component);

    const keyToName = new Map();
    for (const [name, value] of Object.entries(component)) {
      keyToName.set(this.getKey(value), name);
    }
    this[keyToNameSymbol] = keyToName;
  }

  /** Adds a given value, optionally with a given (base) name.
   *
   * @param {*} value Value to add to the component being managed.
   * @param {string=} basename Preferred name for value in component.
   * ({@link ComponentManager#getNames}).
   * @returns {string} Name of added value.
   */
  add(value, basename) {
    const key = this.getKey(value);
    const name = this[keyToNameSymbol].get(key);
    if (name) {
      return name;
    }

    const newName = super.add(value, basename);
    this[keyToNameSymbol].set(key, newName);
    return newName;
  }

  /** Determines if given values match such that an existing value can be used
   * in place of an added value.
   *
   * Overridden to always return false, since values which do not have equal
   * keys do not match (according to this class) and keys are checked before
   * calling super.add().
   *
   * FIXME: This optimization is useful to avoid wasting lots of cycles
   * for properties with many name conflicts with deep values, but it is
   * also a foot-gun if isMatch is called unexpectedly.  Find a better fix.
   *
   * @returns {boolean} False.
   */
  // eslint-disable-next-line class-methods-use-this
  isMatch() {
    return false;
  }
}

/** Converts a value to an key which uniquely identifies the value for
 * matching.
 *
 * @param {*} value Value to identify.
 * @returns {*} Key which uniquely matches the value.
 */
MatchingComponentManager.prototype.getKey = stringify;