WOrmReladb.mjs

import path from 'path'
import events from 'events'
import Sequelize from 'sequelize'
import cloneDeep from 'lodash-es/cloneDeep.js'
import get from 'lodash-es/get.js'
import map from 'lodash-es/map.js'
import each from 'lodash-es/each.js'
import size from 'lodash-es/size.js'
import split from 'lodash-es/split.js'
import genPm from 'wsemi/src/genPm.mjs'
import genID from 'wsemi/src/genID.mjs'
import isarr from 'wsemi/src/isarr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import isobj from 'wsemi/src/isobj.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import arrHas from 'wsemi/src/arrHas.mjs'
import pmSeries from 'wsemi/src/pmSeries.mjs'
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
import pmQueue from 'wsemi/src/pmQueue.mjs'
import WAutoSequelize from 'w-auto-sequelize/src/WAutoSequelize.mjs'
import importModels from './importModels.mjs'
import _genModelsByTabs from './genModelsByTabs.mjs'


//放置於全域宣告, 若使用useStable=true才可於當創建多實例時仍能全域控管
let pmq = pmQueue(1)


/**
 * 操作關聯式資料庫
 *
 * 注意: 各model內id欄位不是主鍵(primary key)時需要強制更改成為主鍵,否則sequelize無法匯入
 *
 * @class
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {String} [opt.url='mssql://username:password@localhost:1433'] 輸入連接資料庫字串,資料庫可選'mssql'、'sqlite'、'mysql'、'mariadb'、'postgres',預設'mssql://username:password@localhost:1433'
 * @param {String} [opt.storage='./worm.db'] 輸入為sqlite時,資料庫檔案所在位置字串,預設'./worm.db'
 * @param {Boolean} [opt.useEncryption=false] 輸入為sqlite時是否使用加密套件[@journeyapps/sqlcipher],型別為布林值,預設false,若是的話因依賴預設不安裝,得自行人工安裝
 * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
 * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
 * @param {String} [opt.fdModels='./models'] 輸入資料表設定檔所在資料夾字串,預設'./models'
 * @param {String} [opt.modelType='js'] 輸入資料表設定檔類型字串,可有'js'、'json',預設'js'
 * @param {Boolean} [opt.logging=false] 輸入是否輸出實際執行的sql指令布林值,預設false
 * @param {String} [opt.pk='id'] 輸入數據主鍵字串,預設'id'
 * @param {Boolean} [opt.autoGenPK=true] 輸入若數據pk(id)欄位沒給時則自動給予隨機uuid,型別為布林值,預設true
 * @param {Boolean} [opt.useStable=true] 輸入是否使用穩定模式,使用佇列管理同時只能進行一種操作故會犧牲效能,sqlite需開啟穩定模式才不會有非預期錯誤,型別為布林值,預設true
 * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
 */
function WOrmReladb(opt = {}) {
    let ss
    let u
    let sequelize = null


    //default
    if (!isestr(opt.url)) {
        opt.url = 'mssql://username:password@localhost:1433'
    }
    if (!isestr(opt.db)) {
        opt.db = 'worm'
    }
    if (!isestr(opt.cl)) {
        opt.cl = 'test'
    }
    if (!isestr(opt.fdModels)) {
        opt.fdModels = './models'
    }
    if (!isestr(opt.modelType)) {
        opt.modelType = 'js'
    }
    opt.logging = (opt.logging === true)
    if (!isestr(opt.pk)) {
        opt.pk = 'id'
    }
    if (!isbol(opt.autoGenPK)) {
        opt.autoGenPK = true
    }
    if (!isbol(opt.useStable)) {
        opt.useStable = true
    }
    if (!isestr(opt.storage)) {
        opt.storage = './worm.db' //storage, only use for sqlite
    }
    opt.storage = path.resolve(opt.storage)
    if (!isbol(opt.useEncryption)) {
        opt.useEncryption = false
    }


    //Op
    let Op = Sequelize.Op


    //ee
    let ee = new events.EventEmitter()


    //dialect
    let dialect
    ss = split(opt.url, '://')
    dialect = get(ss, 0, null)
    if (!dialect) {
        console.log('no dialect in opt.url')
        return ee
    }
    if (!arrHas(dialect, ['mssql', 'sqlite', 'mysql', 'mariadb', 'postgres'])) {
        console.log('invalid dialect in opt.url')
        return ee
    }
    u = get(ss, 1, '') //另存給後面使用


    //username, password
    let username
    let password
    ss = split(u, '@')
    u = get(ss, 1, '') //另存給後面使用
    ss = get(ss, 0, '')
    ss = split(ss, ':')
    if (size(ss) !== 2) {
        console.log('invalid username or password in opt.url')
        return ee
    }
    username = get(ss, 0, '')
    password = get(ss, 1, '')
    if (dialect !== 'sqlite' || (dialect === 'sqlite' && opt.useEncryption)) {
        if (username === '') {
            console.log('invalid username in opt.url')
            return ee
        }
        if (password === '') {
            console.log('invalid password in opt.url')
            return ee
        }
    }


    //host, port
    let host
    let port
    ss = split(u, ':')
    // if (size(ss) !== 2) {
    //     console.log('invalid host or port in opt.url')
    //     return ee
    // }
    host = get(ss, 0, null)
    port = get(ss, 1, null)
    if (dialect !== 'sqlite') {
        if (!host) {
            console.log('invalid host in opt.url')
            return ee
        }
        if (!port) {
            console.log('invalid port in opt.url')
            return ee
        }
    }


    /**
     * 初始化sequelize
     *
     * @memberOf WOrmReladb
     * @param {Boolean} [sync=false] 輸入當importModels匯入models時是否使用同步方式,將models資料變更至資料庫當中,此功能提供給createStorage之用
     * @returns {Promise} 回傳Promise,resolve代表關閉成功,reject回傳錯誤訊息
     */
    async function initSequelize(sync = false) {
        let err = null

        //optSeq
        let optSeq = {
            dialect,
            host,
            port,
            storage: opt.storage,
            logging: opt.logging,
            define: {
                //underscored: false,
                //freezeTableName: false,
                //syncOnAssociation: true,
                //charset: 'utf8',
                //dialectOptions: {
                //  collate: 'utf8_general_ci'
                //},
                timestamps: false
            },
            // pool: {
            //     max: 20,
            //     min: 0,
            //     acquire: 30000, //The maximum time, in milliseconds, that pool will try to get connection before throwing error
            //     idle: 10000 //The maximum time, in milliseconds, that a connection can be idle before being released.
            // },
            //sync: { force: true }, //強制同步
        }
        if (opt.useEncryption && dialect === 'sqlite') {
            optSeq.dialectModulePath = '@journeyapps/sqlcipher' //sqlite加密版
        }

        //sequelize
        sequelize = new Sequelize(opt.db, username, password, optSeq)
        if (opt.useEncryption && dialect === 'sqlite') {
            await sequelize.query('PRAGMA cipher_compatibility = 4') //設置sqlite密鑰相容性
            await sequelize.query(`PRAGMA key = '${username}:${password}'`) //設置slqite密碼為${username}:${password}, 為使用者名稱與密碼用冒號分隔
        }

        //importModels, 若model內id不是pk則需要強制更改成為pk, 否則sequelize無法匯入
        let r = await pm2resolve(importModels)(opt.fdModels, sequelize, opt.cl, { sync, type: opt.modelType })

        //check
        if (r.state === 'error') {
            console.log(r)
            err = `can not import model: ${opt.cl}, need to use genModelsByDB, genModelsByTabs or create ${opt.cl}.${opt.modelType}`
        }

        //mds
        let mds = r.msg

        return {
            mds,
            err,
            close: () => {
                return closeSequelize('external')
            },
        }
    }


    /**
     * 關閉sequelize
     *
     * @memberOf WOrmReladb
     * @returns {Promise} 回傳Promise,resolve代表關閉成功,reject回傳錯誤訊息
     */
    async function closeSequelize(from) {
        if (sequelize !== null) {
            await sequelize.close()
            //console.log(from, 'sequelize.close()')
        }
        sequelize = null
        //console.log(from, 'sequelize = null')
    }


    /**
     * 產生交易transaction狀態物件,當使用transaction時資料庫會上鎖,只能供調用的連線操作處理
     *
     * @memberOf WOrmReladb
     * @returns {Promise} 回傳Promise,resolve回傳交易transaction物件,reject回傳錯誤訊息
     */
    async function genTransaction() {
        let t
        if (sequelize !== null) {
            t = await sequelize.transaction() //使用Unmanaged transaction (then-callback)
            // t.afterCommit(() => {
            //     console.log('afterCommit')
            // })
        }
        else {
            return Promise.reject('invalid sequelize')
        }
        return t
    }


    /**
     * 查詢數據
     *
     * @memberOf WOrmReladb
     * @param {Object} [find={}] 輸入查詢條件物件
     * @param {Object} [option={}] 輸入設定物件,預設為{}
     * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
     * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
     * @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
     */
    async function select(find = {}, option = {}) {

        function cvObj(o) {
            let oNew = {}
            each(o, (v, k) => {
                let kNew = k
                if (k.indexOf('$') >= 0) {
                    k = k.replace('$', '')
                    if (k === 'regex') {
                        kNew = Op.substring
                    }
                    else if (k === 'options') {
                        kNew = null
                    }
                    else if (k === 'nin') {
                        kNew = Op.notIn
                    }
                    else {
                        kNew = Op[k]
                    }
                }
                let vNew = v
                if (isarr(v)) {
                    vNew = cvArray(v)
                }
                else if (isobj(v)) {
                    vNew = cvObj(v)
                }
                if (kNew !== null) {
                    oNew[kNew] = vNew
                }
            })
            return oNew
        }

        function cvArray(o) {
            let oNew = []
            each(o, (v) => {
                let vNew = v
                if (isarr(v)) {
                    vNew = cvArray(v)
                }
                else if (isobj(v)) {
                    vNew = cvObj(v)
                }
                oNew.push(vNew)
            })
            return oNew
        }

        function cvFind(o) {
            let oNew = cvObj(o)
            return oNew
        }

        //find
        if (!isobj(find)) {
            find = {}
        }
        find = cloneDeep(find)

        //useFind
        let useFind = cvFind(find)

        //instance
        let instance = get(option, 'instance', null)

        //transaction
        let transaction = get(option, 'transaction', null)

        //check
        if (opt.useEncryption && dialect === 'sqlite') {
            if (transaction !== null) {
                console.log('@journeyapps/sqlcipher can not support transaction.')
            }
        }

        //si
        let si = instance
        if (instance === null) {
            si = await initSequelize()
        }
        // else {
        //     console.log('select use instance', instance)
        // }

        //rs
        let rs = null
        if (!si.err) {

            //md
            let md = si.mds[opt.cl]

            //setting
            let setting = {
                where: useFind,
                raw: true,
            }
            if (transaction !== null) {
                setting.transaction = transaction
            }

            //findAll
            rs = await md.findAll(setting)

        }
        else {
            ee.emit('error', si.err)
        }

        //closeSequelize
        if (instance === null) { //內部自動初始化得close
            await closeSequelize('select')
        }

        return rs
    }


    /**
     * 插入數據,插入同樣數據會自動產生不同_id,故insert前需自行判斷有無重複
     *
     * @memberOf WOrmReladb
     * @param {Object|Array} data 輸入數據物件或陣列
     * @param {Object} [option={}] 輸入設定物件,預設為{}
     * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
     * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
     * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
     */
    async function insert(data, option = {}) {

        //check
        if (!iseobj(data) && !isearr(data)) {
            return {
                n: 0,
                nInserted: 0,
                ok: 1,
            }
        }

        //instance
        let instance = get(option, 'instance', null)

        //transaction
        let transaction = get(option, 'transaction', null)

        //check
        if (opt.useEncryption && dialect === 'sqlite') {
            if (transaction !== null) {
                console.log('@journeyapps/sqlcipher can not support transaction.')
            }
        }

        //cloneDeep
        data = cloneDeep(data)

        //pm
        let pm = genPm()

        //si
        let si = instance
        if (instance === null) {
            si = await initSequelize()
        }
        // else {
        //     console.log('insert use instance', instance)
        // }

        if (!si.err) {

            //md
            let md = si.mds[opt.cl]

            //check
            if (!isarr(data)) {
                data = [data]
            }

            //n
            let n = size(data)

            //check
            if (opt.autoGenPK) {
                data = map(data, function(v) {
                    if (!v[opt.pk]) {
                        v[opt.pk] = genID()
                    }
                    return v
                })
            }

            //setting
            let setting = { }
            if (transaction !== null) {
                setting.transaction = transaction
            }

            //bulkCreate
            await md.bulkCreate(data, setting)
                .then((res) => {
                    res = { n, nInserted: n, ok: 1 }
                    pm.resolve(res)
                    ee.emit('change', 'insert', data, res)
                })
                .catch(({ original }) => {
                    ee.emit('error', original)
                    pm.reject({ n: 0, ok: 0 })
                })

        }
        else {
            pm.reject(si.err)
        }

        //closeSequelize
        if (instance === null) { //內部自動初始化得close
            await closeSequelize('insert')
        }

        return pm
    }


    /**
     * 儲存數據
     *
     * @memberOf WOrmReladb
     * @param {Object|Array} data 輸入數據物件或陣列
     * @param {Object} [option={}] 輸入設定物件,預設為{}
     * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
     * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
     * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
     * @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
     */
    async function save(data, option = {}) {

        //check
        if (!iseobj(data) && !isearr(data)) {
            return []
        }

        //cloneDeep
        data = cloneDeep(data)

        //instance
        let instance = get(option, 'instance', null)

        //transaction
        let transaction = get(option, 'transaction', null)

        //check
        if (opt.useEncryption && dialect === 'sqlite') {
            if (transaction !== null) {
                console.log('@journeyapps/sqlcipher can not support transaction.')
            }
        }

        //autoInsert
        let autoInsert = get(option, 'autoInsert', true)

        //pm
        let pm = genPm()

        //si
        let si = instance
        if (instance === null) {
            si = await initSequelize()
        }
        // else {
        //     console.log('save use instance', instance)
        // }

        if (!si.err) {

            //md
            let md = si.mds[opt.cl]

            //check
            if (!isarr(data)) {
                data = [data]
            }

            //check
            if (opt.autoGenPK) {
                data = map(data, function(v) {
                    if (!v[opt.pk]) {
                        v[opt.pk] = genID()
                    }
                    return v
                })
            }

            // //tr
            // let t = null
            // let tr = {}
            // if (atomic) {
            //     t = await sequelize.transaction() //使用Unmanaged transaction (then-callback)
            //     tr = {
            //         transaction: t
            //     }
            // }

            //pmSeries
            await pmSeries(data, async function(v) {
                let pmm = genPm()

                //err
                let err = null

                //r
                let r
                if (v[opt.pk]) {
                    //有id

                    //setting
                    let setting = {
                        where: { [opt.pk]: v[opt.pk] },
                        raw: true,
                    }
                    if (transaction !== null) {
                        setting.transaction = transaction
                    }

                    //findOne
                    r = await md.findOne(setting)
                        .catch((error) => {
                            ee.emit('error', error)
                            err = error
                        })

                }
                else {
                    //沒有id
                    err = `${opt.pk} is invalid`
                }

                if (r) {
                    //有找到資料

                    //setting
                    let setting = {
                        where: { [opt.pk]: v[opt.pk] },
                    }
                    if (transaction !== null) {
                        setting.transaction = transaction
                    }

                    let rr = await md.update(v, setting)
                        .catch((error) => {
                            ee.emit('error', error)
                            err = error
                        })

                    if (rr) {
                        //console.log('update 有更新資料', rr)
                        pmm.resolve({ n: 1, nModified: 1, ok: 1 })
                    }
                    else {
                        //console.log('update 沒有更新資料', err)
                        pmm.resolve({ n: 1, nModified: 0, ok: 0 })
                    }

                }
                else {
                    //沒有找到資料

                    //autoInsert
                    if (autoInsert) {

                        //setting
                        let setting = { }
                        if (transaction !== null) {
                            setting.transaction = transaction
                        }

                        //create
                        let rr = await md.create(v, setting)
                            .catch((error) => {
                                ee.emit('error', error)
                                err = error
                            })

                        if (rr) {
                            //console.log('create 有插入資料', rr)
                            pmm.resolve({ n: 1, nInserted: 1, ok: 1 })
                        }
                        else {
                            //console.log('create 沒有插入資料', err)
                            pmm.resolve({ n: 1, nInserted: 0, ok: 0 })
                        }

                    }
                    else {
                        //console.log('findOne 沒有找到資料也不自動插入', err)
                        pmm.resolve({ n: 0, nModified: 0, ok: 1 })
                    }

                }

                pmm._err = err //避免eslint錯誤訊息
                return pmm
            })
                .then((res) => {
                    pm.resolve(res)
                    ee.emit('change', 'save', data, res)
                })
                .catch((error) => {
                    pm.reject(error)
                })

        }
        else {
            pm.reject(si.err)
        }

        //closeSequelize
        if (instance === null) { //內部自動初始化得close
            await closeSequelize('save')
        }

        return pm
    }


    /**
     * 刪除數據
     *
     * @memberOf WOrmReladb
     * @param {Object|Array} data 輸入數據物件或陣列,會查找各數據的opt.pk值,有存在者就刪除
     * @param {Object} [option={}] 輸入設定物件,預設為{}
     * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
     * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
     * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
     */
    async function del(data, option = {}) {

        //check
        if (!iseobj(data) && !isearr(data)) {
            return []
        }

        //cloneDeep
        data = cloneDeep(data)

        //instance
        let instance = get(option, 'instance', null)

        //transaction
        let transaction = get(option, 'transaction', null)

        //check
        if (opt.useEncryption && dialect === 'sqlite') {
            if (transaction !== null) {
                console.log('@journeyapps/sqlcipher can not support transaction.')
            }
        }

        //pm
        let pm = genPm()

        //si
        let si = instance
        if (instance === null) {
            si = await initSequelize()
        }
        // else {
        //     console.log('del use instance', instance)
        // }

        if (!si.err) {

            //md
            let md = si.mds[opt.cl]

            //check
            if (!isarr(data)) {
                data = [data]
            }

            //pmSeries
            await pmSeries(data, async function(v) {
                let pmm = genPm()

                //err
                let err = null

                //r
                let r
                if (v[opt.pk]) {
                    //有id

                    //setting
                    let setting = {
                        where: { [opt.pk]: v[opt.pk] },
                        raw: true,
                    }
                    if (transaction !== null) {
                        setting.transaction = transaction
                    }

                    //findOne
                    r = await md.findOne(setting)
                        .catch((error) => {
                            ee.emit('error', error)
                            err = error
                        })

                }
                else {
                    //沒有id
                    err = `${opt.pk} is invalid`
                }

                if (r) {
                    //有找到資料

                    //setting
                    let setting = {
                        where: { [opt.pk]: v[opt.pk] },
                    }
                    if (transaction !== null) {
                        setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
                    }

                    //destroy
                    let rr = await md.destroy(setting)
                        .catch((error) => {
                            ee.emit('error', error)
                            err = error
                        })

                    if (rr) {
                        //console.log('destroy 有刪除資料', rr)
                        pmm.resolve({ n: 1, nDeleted: 1, ok: 1 })
                    }
                    else {
                        //console.log('destroy 沒有刪除資料', err)
                        pmm.resolve({ n: 1, nDeleted: 0, ok: 0 })
                    }

                }
                else {
                    //console.log('findOne 沒有找到資料', err)
                    pmm.resolve({ n: 0, nDeleted: 0, ok: 1 })
                }

                pmm._err = err //避免eslint錯誤訊息
                return pmm
            })
                .then((res) => {
                    pm.resolve(res)
                    ee.emit('change', 'del', data, res)
                })
                .catch((res) => {
                    pm.reject(res)
                })

        }
        else {
            pm.reject(si.err)
        }

        //closeSequelize
        if (instance === null) { //內部自動初始化得close
            await closeSequelize('del')
        }

        return pm
    }


    /**
     * 刪除數據,需與del分開,避免未傳數據導致直接刪除全表
     *
     * @memberOf WOrmReladb
     * @param {Object} [find={}] 輸入刪除條件物件,不給予find則代表刪除全部數據
     * @param {Object} [option={}] 輸入設定物件,預設為{}
     * @param {Object} [option.instance=null] 輸入實例instance物件,預設為null
     * @param {Object} [option.transaction=null] 輸入交易(transaction)物件,預設為null
     * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
     */
    async function delAll(find = {}, option = {}) {

        //find
        if (!isobj(find)) {
            find = {}
        }
        find = cloneDeep(find)

        //instance
        let instance = get(option, 'instance', null)

        //transaction
        let transaction = get(option, 'transaction', null)

        //check
        if (opt.useEncryption && dialect === 'sqlite') {
            if (transaction !== null) {
                console.log('@journeyapps/sqlcipher can not support transaction.')
            }
        }

        //pm
        let pm = genPm()

        //si
        let si = instance
        if (instance === null) {
            si = await initSequelize()
        }
        // else {
        //     console.log('delAll use instance', instance)
        // }

        if (!si.err) {

            //md
            let md = si.mds[opt.cl]

            //setting
            let setting = {
                where: find,
            }
            if (transaction !== null) {
                setting.transaction = transaction //destroy的說明寫不需transaction但實際需要 (2020/10/07)
            }

            //destroy
            await md.destroy(setting)
                .then((res) => {
                    res = { n: res, nDeleted: res, ok: 1 }
                    pm.resolve(res)
                    ee.emit('change', 'delAll', null, res)
                })
                .catch((error) => {
                    ee.emit('error', error)
                    pm.reject({ n: 0, ok: 0 })
                })

        }
        else {
            pm.reject(si.err)
        }

        //closeSequelize
        if (instance === null) { //內部自動初始化得close
            await closeSequelize('delAll')
        }

        return pm
    }


    /**
     * 創建sqlite資料庫檔案
     *
     * @memberOf WOrmReladb
     * @returns {Promise} 回傳Promise,resolve回傳創建結果,reject回傳錯誤訊息
     */
    async function createStorage() {

        //pm
        let pm = genPm()

        //initSequelize
        let si = await initSequelize(true)

        //closeSequelize
        await closeSequelize('createStorage')

        //check
        if (si.err) {
            pm.reject(si.err)
        }
        else {
            pm.resolve('created')
        }

        return pm
    }


    /**
     * 由指定資料庫生成各資料表的models資料
     *
     * 目前僅能產生js檔格式,且mssql欄位為nvarchar(MAX)無法自動轉成TEXT格式
     *
     * include from: [w-auto-sequelize](https://github.com/yuda-lyu/w-auto-sequelize)
     *
     * @memberOf WOrmReladb
     * @param {Object} [option={}] 輸入設定物件,預設{}
     * @param {String} [option.storage='./worm.db'] 輸入sqlite資料庫檔案位置字串,預設'./worm.db'
     * @param {String} [option.db='worm'] 輸入資料庫名稱字串,預設'worm'
     * @param {String} [option.username='username'] 輸入使用者名稱字串,預設'username'
     * @param {String} [option.password='password'] 輸入密碼字串,預設'password'
     * @param {String} [option.dialect='mssql'] 輸入資料庫種類字串,預設'mssql',可選'mssql', 'sqlite', 'mysql', 'mariadb', 'postgres'
     * @param {String} [option.fdModels='./models'] 輸入models儲存的資料夾名稱字串,預設'./models'
     * @param {String} [option.host='localhost'] 輸入連線主機host位址字串,預設'localhost'
     * @param {Integer} [option.port=1433] 輸入連線主機port整數,預設1433
     * @returns {Promise} 回傳Promise,resolve回傳產生的models資料,reject回傳錯誤訊息
     */
    function genModelsByDB(option = {}) {

        //default
        let def = {
            username: 'username',
            password: 'password',
            dialect: 'mssql',
            host: 'localhost',
            port: 1433,
            storage: './worm.db',
        }

        //merge
        option = {
            ...def,
            ...option,
        }

        //database
        if (!option.db) {
            option.db = 'worm'
        }
        option.database = opt.db

        //directory
        if (!option.fdModels) {
            option.fdModels = './models'
        }
        option.directory = opt.fdModels

        //storage
        option.storage = path.resolve(option.storage)

        //WAutoSequelize
        return WAutoSequelize(option)

    }


    /**
     * 由資料表物件生成各資料表的models資料
     *
     * @memberOf WOrmReladb
     * @param {String} [fd='./models'] 輸入models儲存的資料夾名稱字串,預設'./models'
     * @param {Object} [tabs={}] 輸入各資料表物件,預設{}
     * @param {Object} [opt={}] 輸入設定物件,預設{}
     * @param {String} [opt.type='js'] 輸入資料表類型字串,預設'js'
     */
    function genModelsByTabs(...input) {
        _genModelsByTabs(...input)
    }


    //bind
    ee.createStorage = createStorage
    ee.genModelsByDB = genModelsByDB
    ee.genModelsByTabs = genModelsByTabs
    ee.init = initSequelize
    // ee.close = () => {
    //     return closeSequelize('external')
    // }
    ee.genTransaction = genTransaction
    if (opt.useStable) {
        //用佇列(同時最大執行數1且先進先執行)處理高併發之情形
        //若沒管控:
        //mssql就是先執行先贏
        //Sqlite有機會無法儲存, 或是出錯[Error: SQLITE_MISUSE: bad parameter or other API misuse]或[Error: SQLITE_MISUSE: Database handle is closed]
        ee.select = function() {
            return pmq(select, ...arguments)
        }
        ee.insert = function() {
            return pmq(insert, ...arguments)
        }
        ee.save = function() {
            return pmq(save, ...arguments)
        }
        ee.del = function() {
            return pmq(del, ...arguments)
        }
        ee.delAll = function() {
            return pmq(delAll, ...arguments)
        }
    }
    else {
        ee.select = select
        ee.insert = insert
        ee.save = save
        ee.del = del
        ee.delAll = delAll
    }

    return ee
}


export default WOrmReladb