parks/database.js

import ConfigBase from '../configBase.js';
import sieve from './sieve.js';
import {reusePromise, reusePromiseForever} from '../reusePromises.js';
import HTTP from './http.js';
import Cache from '../cache/scopedCache.js';

const Databases = {};

/**
 * A class that handles fetching and storing data for a resort/park
 */
export class Database extends ConfigBase {
  /**
   * Construct a new Database object
   * @param {object} options
   */
  constructor(options = {}) {
    options.useragent = options.useragent || null;

    super(options);

    this.cache = new Cache(this.constructor.name, this.config.cacheVersion || 0);

    this.http = new HTTP();
    if (this.config.useragent) {
      this.http.useragent = this.config.useragent;
    }

    this.http.injectForDomain({hostname: {$exists: true}}, (method, url) => {
      this.log(method, url);
    });
  }

  /**
   * Pretty print log for this database
   */
  log(...args) {
    console.log(`[\x1b[32m${this.constructor.name}\x1b[0m]`, ...args);
  }

  /**
   * Get singleton of this class type
   * @param {object} [options] Options to pass to new instance.
   * Will only be used if the instance doesn't already exist
   * @return {Database}
   */
  static get(options = {}) {
    const className = this.name;
    if (Databases[className] === undefined) {
      Databases[className] = new this(options);
    }
    return Databases[className];
  }

  /**
   * Initialise this database object
   */
  async init() {
    // only ever call _init once
    await reusePromiseForever(this, this._init);
  }

  /**
   * Internal init function, override this in child classes for functionality
   * @abstract
   */
  async _init() {}

  /**
   * Get entities from this database. Optionally filtering by some conditions
   * @param {object} [filter]
   */
  async getEntities(filter = {}) {
    await this.init();

    const entities = await this.cache.wrap('entities', () => {
      return reusePromise(this, this._getEntities);
    }, 1000 * 60); // cache for a minute for faster access

    const filterFn = sieve(filter);
    return entities.filter(filterFn);
  }

  /**
   * Find a single entity from this database
   * @param {object} [filter]
   */
  async findEntity(filter = {}) {
    const ents = await this.getEntities();
    const filterFn = sieve(filter);
    return ents.find(filterFn);
  }

  /**
   * Return all entities for this resort/park
   * @abstract
   * @return {array<object>}
   */
  async _getEntities() {
    throw new Error(`Database class ${this.constructor.name} missing _getEntities() function`);
  }

  /**
   * Get an entity object from it's ID
   * @param {string} entityId Entity ID
   */
  async getEntitiyById(entityId) {
    return await this.findEntity({
      id: `${entityId}`,
    });
  }
}

export default Database;