I'm rewriting some old Chrome extension code while simultaneously trying to learn new ES6 tricks, and I'm running into some design questions.
My goal is to provide a value storage (which is backed by the asynchronous chrome.storage for persistence, but that's outside the scope of the question). What I want is to associate some validation with the values. So, my Storage is a collection of Values, each associated with a validation function.
In my old version, I would just pass a validation function when I instantiate a value, something like this (simplified):
Storage["key1"] = new Value({
  validator: ValidatorIsInteger, defaultValue: 0, /* ... */
});
Storage["key2"] = new Value({
  validator: ValidatorEnum(["a", "b", "c"]), defaultValue: "a", /* ... */
});
However, I'm trying to rewrite this with Value being a class that can be extended with specific validators, which seemed to be a good idea at the time. Again, simplified:
class Value {
  constructor(key, defaultValue) {
    this.key = key;
    this.defaultValue = defaultValue;
    this.set(defaultValue);
  }
  set(newValue) {
    var validationResult = this.validate(newValue);
    if (validationResult.pass) {
      this.value = newValue;
      return newValue;
    } else {
      throw new RangeError(
        `Value ${newValue} for ${this.key} failed validation: ${validationResult.message}`
      );
    }
  }
  get() { return this.value; }
  // Overload in children
  validate(value) {
    return {pass: true};
  }
}
class IntegerValue extends Value {
  validate(value) {
    if (Number.isInteger(value)) {
      return {pass: true};
    } else {
      return {pass: false, message: "Value must be an integer"};
    }
  }
}
So far so good. However, I run into problems when trying to make a parametrized child class:
class EnumValue extends Value {
  constructor(key, defaultValue, possibleValues) {
    this.possibleValues = possibleValues; // NUH-UH, can't set that before super()
    super(key, defaultValue);
  }
  // Will be called from parent constructor
  validate(value) {
    if (this.possibleValues.includes(value)) {
      return {pass: true};
    } else {
      return {pass: false, message: `Value must be in [${this.possibleValues}]`};
    }
  }
}
The problem is in "setting up" the parametrized validator before .set(defaultValue) is called. I see several ways out of this, all of which seems lacking:
- Resign, and not use the class-extension-based approach - I want to see if it can be fixed first.
- Always trust the default value as a workaround to calling .set(defaultValue)- bad, because I don't want accidentally inconsistent data.
- Make .set()asynchronous, giving the constructor a chance to finish before validation is performed - while the persistence backend is asynchronous, the purpose ofStorageis, among other things, to provide a synchronous "cache".
Am I failing to see some obvious fix to this approach? If not, and this is simply a wrong tool for the job, how should I re-organize this?
 
     
    