FunNode/npm-mysql

View on GitHub
index.js

Summary

Maintainability
A
0 mins
Test Coverage
/* global R5 */

module.exports = Database;

if (!global.R5) {
  global.R5 = {
    out: console
  };
}

const mysql = require('promise-mysql');

// Constructor

function Database (read_config, write_config = false) {
  this.IN = new Host(read_config);

  if (write_config && read_config['host'] !== write_config['host']) {
    this.OUT = new Host(write_config);
  }
  else {
    this.OUT = this.IN;
  }
}

function Host (config) {
  this.connect_retries = 0;
  this.query_retries = 0;
  this.error_timeout = 10000;
  this.config = config;
  this.config.reconnect = false;
}

// Public Methods

Database.prototype = {
  connect: async function () {
    await this.IN.connect();
    if (this.OUT !== this.IN) {
      await this.OUT.connect();
    }
  },

  disconnect: function () {
    this.IN.destroy();
    if (this.OUT !== this.IN) {
      this.OUT.destroy();
    }
  },

  query: async function (query) {
    let host = this.IN;
    if (
      (typeof query === 'string' && is_update(query)) ||
      (typeof query === 'object' && is_update(query.sql))
    ) {
      host = this.OUT;
    }
    return host.query(query);
  },
};

Host.prototype = {
  connect: async function () {
    const host = this;
    try {
      this.connection = await mysql.createConnection(host.config);
    }
    catch (err) {
      if (host.connect_retries++ < 10) {
        R5.out.error(`MySQL connecting (retrying [${host.connect_retries}]): ${err.code}`);
        return host.retry();
      }
      R5.out.error(`MySQL connecting: ${err.stack}`);
      throw err;
    }
    R5.out.log(`MySQL connected (conn: ${host.connection.connection.threadId })`);
    this.connect_retries = 0;
    host.connection.on('error', async function (err) {
      R5.out.error((err.fatal === true ? '(FATAL) ' : '') + err);
      if (err.code === 'PROTOCOL_CONNECTION_LOST' || err.fatal === true) {
        host.destroy();
        return host.connect();
      }
      else {
        throw err;
      }
    });  
  },
  
  destroy: function () {
    const host = this;
    if ((host.connection || {}).destroy) {
      host.connection.destroy();
    }
  },

  query: async function (query) {
    const host = this;
    try {
      const res = await host.connection.query(query);
      this.query_retries = 0;
      return res;
    }
    catch (err) {
      if (err.fatal && host.query_retries++ < 10) {
        return host.retry(query);
      }
      R5.out.error(`MySQL query error: ${err}\n${query}\n`);
      throw err;
    }
  },

  retry: async function (query) {
    const host = this;
    host.destroy();
    await delay(host.error_timeout * (host.connect_retries + host.query_retries + 1));
    await host.connect();
    if (query) {
      return host.query(query);
    }
  },
};

// Private Methods

function is_update (str) {
  return str.substring(0, 6).toUpperCase() !== 'SELECT';
}

function delay (ms) {
  return new Promise((res) => setTimeout(res, ms));
}