UNPKG

futoin-database

Version:

Neutral database interface with powerful Query and revolution Transaction builders

228 lines (204 loc) 7.29 kB
'use strict'; /** * @file * * Copyright 2017 FutoIn Project (https://futoin.org) * Copyright 2017 Andrey Galkin <andrey@futoin.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const _cloneDeep = require( 'lodash/cloneDeep' ); const _extend = require( 'lodash/extend' ); const _defaults = require( 'lodash/defaults' ); const Executor = require( 'futoin-executor/Executor' ); const L1Face = require( './L1Face' ); const L2Face = require( './L2Face' ); const L1Service = require( './L1Service' ); const L2Service = require( './L2Service' ); // auto-configure QueryBuilder drivers const main = require( './main' ); void main; const serviceImpl = { mysql : './MySQLService', postgresql : './PostgreSQLService', sqlite : './SQLiteService', }; /** * @private * @param {AsyncSteps} as - steps interface * @param {AdvancedCCM} ccm - CCM * @param {string} name - iface name * @param {object} options - service options */ function create( as, ccm, name, options ) { const factory = serviceImpl[options.type]; let service; if ( typeof factory === "string" ) { service = require( factory ); } else if ( typeof factory === "function" && factory.prototype instanceof L1Service ) { service = factory; } else if ( typeof factory === "function" ) { service = factory(); } else { as.error( 'InternalError', `Unknown factory type ${factory} for ${options.type}` ); } const executor = new Executor( ccm ); service = service.register( as, executor, options ); if ( service instanceof L2Service ) { L2Face.register( as, ccm, `#db.${name}`, executor ); } else if ( service instanceof L1Service ) { L1Face.register( as, ccm, `#db.${name}`, executor ); } else { as.error( 'InternalError', `Unknown service type "${typeof service}" for ${name}` ); } if ( name === 'default' ) { ccm.alias( `#db.${name}`, '#db' ); } if ( !ccm.db ) { /** * Retrieve database interface. * * @name AdvancedCCM#db * @param {string} [name=default] - connection name * @returns {object} FTN14 native face * @note Monkey-patched only for related CCM */ ccm.db = function( name ) { return this.iface( '#db.' + ( name || "default" ) ); }; } // Make sure to show unexpected internal errors to user executor.on( 'notExpected', function() { try { ccm.log().error( 'Not expected DB service error' ); ccm.log().error( `${arguments[0]}: ${arguments[3]}` ); } catch ( e ) { console.error( 'Not expected DB service error' ); console.error( arguments ); } } ); // Make sure to shutdown processing on CCM close ccm.on( 'close', () => executor.close() ); } /** * @brief Automatically configure database connections * and related internal Executors. * * For each config entry an instance of dedicated * Executor with registered database service is created and * related interface is registered on CCM. * * Interfaces are registered as "#db.{key}". The "default" one * is also aliased as "#db". * * Env patterns to service configuration: * - DB_{name}_HOST -> host * - DB_{name}_PORT -> port * - DB_{name}_SOCKET -> port (overrides DB_PORT) * - DB_{name}_USER -> user * - DB_{name}_PASS -> password * - DB_{name}_DB -> database * - DB_{name}_MAXCONN -> conn_limit * - DB_{name}_TYPE - type of database, fails if mismatch configuration * Note: the variables names are driven by CodingFuture CFDB Puppet module. * * The "default" key also tries env without "{name}_" infix. * * Example: * ```javascript * AutoConfig(ccm, { * "default": { * type: ["mysql", "postgresql"], * // DB_DEFAULT_TYPE or DB_TYPE must match any of them * }, * readonly: { * type: "mysql" * // fail, if DB_READONLY_TYPE != mysql * }, * preset: { * type: "postgresql", * host: "127.0.0.1", * port: 5432, * user: "test", * password: "test", * database: "test", * conn_limit: 10, * // no need to env variables - all is preset * }, * }) * ``` * * @name AutoConfig * @param {AsyncSteps} as - async steps interface * @param {AdvancedCCM} ccm - CCM instance * @param {object} [config=null] - expected connection key => type map * @param {object} [env=process.env] - source of settings * * @note it also monkey patches CCM with #db(name="default") method */ module.exports = function( as, ccm, config=null, env=process.env ) { config = config || { default: {} }; for ( let name in config ) { let uname = name.toUpperCase(); let options = _cloneDeep( config[name] ); let detected = { type: env[`DB_${uname}_TYPE`], host: env[`DB_${uname}_HOST`], port: env[`DB_${uname}_SOCKET`] || parseInt( env[`DB_${uname}_PORT`] || '0' ) || undefined, user: env[`DB_${uname}_USER`], password: env[`DB_${uname}_PASS`] || undefined, database: env[`DB_${uname}_DB`] || undefined, conn_limit: parseInt( env[`DB_${uname}_MAXCONN`] || '0' ) || undefined, }; if ( name === 'default' ) { _defaults( detected, { type: env[`DB_TYPE`], host: env[`DB_HOST`], port: env[`DB_SOCKET`] || parseInt( env[`DB_PORT`] || '0' ) || undefined, user: env[`DB_USER`], password: env[`DB_PASS`] || undefined, database: env[`DB_DB`] || undefined, conn_limit: parseInt( env[`DB_MAXCONN`] || '0' ) || undefined, } ); } _defaults( detected, { conn_limit: 1 } ); let db_type = options.type; if ( db_type ) { if ( db_type instanceof Array && db_type.indexOf( detected.type ) >= 0 ) { // pass } else if ( db_type === detected.type ) { // pass } else { as.error( 'InternalError', `DB type mismatch for ${name}: ${db_type} != ${detected.type}` ); } } _extend( options, detected ); create( as, ccm, name, options ); } }; /** * Register database service type. * @name AutoConfig.register * @param {string} type - type of database * @param {string|callable|object} factory - module name, factory method * or a subclass of L1Service */ module.exports.register = function( type, factory ) { serviceImpl[type] = factory; };