UNPKG

sqmicro-connection

Version:

Connection abstraction for SQ Micro.

205 lines (180 loc) 7.66 kB
/** * @module sqmicro-connection * Соединение. Может восстанавливаться после сбоев. Требуется драйвер соединения, * например один из модулей `sqmicro-driver-*`. Класс драйвера должен реализовать * интерфейсы IConnectionDriver, ICopier и, возможно, IQuerist. * * Драйвер соединения * @interface IConnectionDriver * @property {string} driverName имя драйвера, только чтение. * @property {boolean} isConnected установлено ли соединение (только чтение)? * * @async @method connect установить соединение * @returns {Promise<this, Error>} * * @async @method disconnect разорвать соединение. * @returns {Promise<this, Error>} * * @event connected соединение установлено. * @event error ошибка соединения. * @event end соединение закрыто пользователем. * @event disconnected соединение разорвано вследствие ошибки. * * Копировщик. * @interface ICopier * @async @method getCopyStream Получить поток для копирования данных. * @param {string} dest место назначения данных. Интерпретируется драйвером. * @param {object} options * @param {Array<string>} options.fieldNames список всех имен полей. Колонки в * строках сформированы в соответствии с этим массивом. * @param {string} options.delimiter разделитель колонок. * @returns {Promise} * * SQL запросы. * @interface IQuerist * @async @method query Выполнить SQL запрос * @param {string} sql SQL запрос. * @param {Array<*>} values значения переменных в SQL запросе. * @returns {Promise<ResultSet, Error>} */ const url = require('url'); const { bindEvents, promisifyEventEmitter, Retrier } = require('sqmicro-commons'); const formatterFactory = require('./formatter-factory'); const Private = Symbol('Private'); const EVENTS = { disconnected: function(connection) { connection.connect(); } }; /** * Создать объект "соединение" используя данный драйвер в качестве базового класса. * @param {function} Driver класс драйвера. * @returns {function} класс соединения. */ const connectionFactory = (Driver) => class Connection extends Driver { /** * URL соединения без кредов, например для логирования. * @type {string} */ get safeUrl() { return url.format(this[Private].url, { auth: false }); } /** * Создать экземпляр соединения. * @param {object} config * @param {string} config.connectionString URL подключения. * @param {object?|boolean} config.retry Настройки восстановления соединения. * Подробности и настройки по умолчанию - см. Retrier. Если true — * использовать настройки по умолчанию, если undefined — не повторять попыток * соединения. */ constructor(config) { super(config); const pvt = this[Private] = { }; pvt.url = new url.URL(config.connectionString); pvt.retrier = createRetrier(config.retry); bindEvents(this, EVENTS, null, [this]); } /** * Установить соединение. При ошибке - повторять согласно настройкам. * @returns {Promise<Connection, Error>} */ async connect() { const pvt = this[Private]; await pvt.retrier.retry( () => super.connect(), `connecting to ${this.safeUrl}` ); pvt.isConnected = true; return this; } /** * Копировать данные в место назначения. * @param {string} dest имя места назначения. Интерпретируется драйвером. * @param {iterable<object>} rows строки данных. * @param {object} options опции * @param {Array<string>} options.fieldNames список имен полей. * @param {string} options.delimiter символ-разделитель колонок в строках. * @returns {Promise} */ async copy(dest, rows, options) { ensureConnected(this); const formatter = formatterFactory(options); formatter.appendStream( await super.getCopyStream(dest, options) ); for (const row of rows) { formatter.write(row); } return await formatter.end(); } /** * Выполнить SQL запрос. Может не поддерживаентся драйвером. * @todo @typedef {object} ResultSet * @returns {ResultSet} */ async query(...args) { ensureConnected(this); return await super.query(...args); } }; /** * Создать класс соединения, используя данный драйвер. * @todo check Driver implements `IConnectionDriver` * @param {function|string} driver либо класс, либо имя драйвера. В * последнем случае клиентскому проекту должен быть доступен соответствующий * модуль, например если driver = 'pg', то нужен модуль 'sqmicro-driver-pg'; * @returns {function} класс соединения. * @throws {TypeError} если аргумент не строка и не класс. * @throws {Error} если модуль драйвера недоступен. */ module.exports = function(driver) { let type = typeof driver; if (type === 'string') { driver = getDriver(driver); type = typeof driver; } if (type !== 'function') { throw TypeError(`driver should be either class or a string`); } return connectionFactory(driver); }; /** * Создать экземпляр класса Retrier. * @inner * @param {object?} options. * @returns {Retrier} */ function createRetrier(options) { return !options ? new Retrier(undefined, 1) : typeof(options) === 'object' ? new Retrier(options.delay, options.maxAttempts) : new Retrier(); } /** * Загрузить модуль драйвера. Модуль должен экспортировать класс. * @inner * @param {string} driverName * @returns {*} модуль. */ function getDriver(driverName) { const moduleName = `sqmicro-connection-${driverName}`.toLocaleLowerCase(); try { return require(moduleName); } catch(e) { throw Error(`Please install ${moduleName} to use driver ${driverName}`); } } /** * Убедиться, что соединение устанвлено. * @inner * @param {Connection} connection * @throws {Error} если соединеине не установлено или разорвано. */ function ensureConnected(connection) { if (!connection.isConnected) { throw Error(`Can't operate on a disconnected ${connection.safeUrl}`); } }