import events from 'events'
import mongodb from 'mongodb'
import stream from 'stream'
import cloneDeep from 'lodash-es/cloneDeep.js'
import get from 'lodash-es/get.js'
import map from 'lodash-es/map.js'
import omit from 'lodash-es/omit.js'
import size from 'lodash-es/size.js'
import genPm from 'wsemi/src/genPm.mjs'
import genID from 'wsemi/src/genID.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isarr from 'wsemi/src/isarr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import pmSeries from 'wsemi/src/pmSeries.mjs'
// //optMGConn
// let optMGConn = {
// useNewUrlParser: true,
// useUnifiedTopology: true
// }
/**
* 操作資料庫(MongoDB)
*
* @class
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {String} [opt.url='mongodb://127.0.0.1:27017'] 輸入連接資料庫字串,預設'mongodb://127.0.0.1:27017'
* @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
* @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
* @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
*/
function WOrmMongodb(opt = {}) {
//default
if (!opt.url) {
opt.url = 'mongodb://127.0.0.1:27017'
}
if (!opt.db) {
opt.db = 'worm'
}
if (!opt.cl) {
opt.cl = 'test'
}
//ee
let ee = new events.EventEmitter()
//MongoClient
let MongoClient = mongodb.MongoClient
/**
* 查詢數據
*
* @memberOf WOrmMongodb
* @param {Object} [find={}] 輸入查詢條件物件
* @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
*/
async function select(find = {}) {
let isErr = false
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
let collection = database.collection(opt.cl)
//find
let cursor = collection.find(find)
//.sort({ $natural: -1 })
//.limit(N)
//toArray
res = await cursor.toArray()
//omit
res = map(res, function(v) {
v = omit(v, '_id')
return v
})
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 插入數據,插入同樣數據會自動產生不同_id,故insert前需自行判斷有無重複
*
* @memberOf WOrmMongodb
* @param {Object|Array} data 輸入數據物件或陣列
* @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
*/
async function insert(data) {
let isErr = false
//check
if (!iseobj(data) && !isearr(data)) {
return {
n: 0,
nInserted: 0,
ok: 1,
}
}
//cloneDeep
data = cloneDeep(data)
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
let collection = database.collection(opt.cl)
//check
if (!isarr(data)) {
data = [data]
}
//check id
data = map(data, function(v) {
if (!isestr(v.id)) {
v.id = genID()
}
return v
})
//insertMany
res = await collection.insertMany(data)
//check
if (res.insertedCount > 0) {
//res
res = {
n: size(data),
nInserted: res.insertedCount,
ok: res.acknowledged ? 1 : 0,
}
//emit
ee.emit('change', 'insert', data, res)
}
else {
isErr = true
//res
res = `no insert data`
}
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 儲存數據
*
* @memberOf WOrmMongodb
* @param {Object|Array} data 輸入數據物件或陣列
* @param {Object} [option={}] 輸入設定物件,預設為{}
* @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
* @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
*/
async function save(data, option = {}) {
let isErr = false
//check
if (!iseobj(data) && !isearr(data)) {
return []
}
//cloneDeep
data = cloneDeep(data)
//autoInsert
let autoInsert = get(option, 'autoInsert', true)
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
let collection = database.collection(opt.cl)
//check
if (!isarr(data)) {
data = [data]
}
//check id
data = map(data, function(v) {
if (!isestr(v.id)) {
v.id = genID()
}
return v
})
//pmSeries
res = await pmSeries(data, async(v) => {
//rest
let rest = null
//oper
rest = await collection.findOneAndUpdate({ id: v.id }, { $set: v })
// console.log('rest', rest)
if (iseobj(rest)) {
rest = {
n: 1,
nModified: 1,
ok: 1,
}
}
else {
rest = {
n: 0,
nModified: 0,
ok: 1,
}
}
//autoInsert
if (autoInsert && rest.n === 0) {
rest = await insert(v)
}
return rest
})
//emit
ee.emit('change', 'save', data, res)
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 刪除數據
*
* @memberOf WOrmMongodb
* @param {Object|Array} data 輸入數據物件或陣列
* @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
*/
async function del(data) {
let isErr = false
//check
if (!iseobj(data) && !isearr(data)) {
return []
}
//cloneDeep
data = cloneDeep(data)
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
let collection = database.collection(opt.cl)
//check
if (!isarr(data)) {
data = [data]
}
//pmSeries
res = await pmSeries(data, async(v) => {
//rest
let rest = null
//deleteOne
rest = await collection.deleteOne({ id: v.id })
//rest
rest = {
n: rest.deletedCount,
nDeleted: rest.deletedCount,
ok: rest.acknowledged ? 1 : 0,
}
return rest
})
//emit
ee.emit('change', 'del', data, res)
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
*
* @memberOf WOrmMongodb
* @param {Object} [find={}] 輸入刪除條件物件
* @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
*/
async function delAll(find = {}) {
let isErr = false
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
let collection = database.collection(opt.cl)
//deleteMany
res = await collection.deleteMany(find)
//res
res = {
n: res.deletedCount,
nDeleted: res.deletedCount,
ok: res.acknowledged ? 1 : 0,
}
//emit
ee.emit('change', 'delAll', null, res)
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 使用GridFS,插入數據,需為Uint8Array格式
*
* @memberOf WOrmMongodb
* @param {Uint8Array} u8a
* @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
*/
async function insertGfs(u8a) {
let isErr = false
//id
let id = genID()
//buf
let buf = Buffer.from(u8a)
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//core
let core = async (id, buf) => {
//pm
let pm = genPm()
//database
let database = client.db(opt.db)
//bucket
let bucket = new mongodb.GridFSBucket(database, {
chunkSizeBytes: 10 * 1024 * 1024, //10mb
bucketName: opt.cl
})
//stream
let sm = new stream.Readable()
sm._read = () => {}
sm.push(buf)
sm.push(null)
sm.pipe(bucket.openUploadStream(id)) //pipe是接bucket的Writable, 所以會監聽finish
.on('error', function(err) {
//reject
pm.reject(err)
})
.on('finish', function() {
//res
let res = { n: 1, ok: 1, id }
//resolve
pm.resolve(res)
})
return pm
}
//res
let res = null
try {
//core
res = await core(id, buf)
//emit
ee.emit('change', 'insertGfs', null, res)
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 使用GridFS,查詢數據
*
* @memberOf WOrmMongodb
* @param {String} id 輸入查詢id字串
* @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
*/
async function selectGfs(id) {
let isErr = false
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//core
let core = async (id) => {
//pm
let pm = genPm()
//database
let database = client.db(opt.db)
//bucket
let bucket = new mongodb.GridFSBucket(database, {
chunkSizeBytes: 10 * 1024 * 1024, //10mb
bucketName: opt.cl
})
//buf
let buf = Buffer.from('')
//stream
let sm = bucket.openDownloadStreamByName(id)
sm.on('data', function (chunk) {
buf = Buffer.concat([buf, chunk])
})
sm.on('error', function (err) {
//reject
pm.reject(err)
})
sm.on('end', function () {
//u8a
let u8a = new Uint8Array(buf)
//clean memory
buf = null
//resolve
pm.resolve(u8a)
})
return pm
}
//res
let res = null
try {
//core
res = await core(id)
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
async function _findGfs(find = {}, bucket) {
let isErr = false
//res
let res = null
try {
//find
let cursor = bucket.find(find)
//toArray
res = await cursor.toArray()
}
catch (err) {
isErr = true
res = err
}
finally {
// await client.close()
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 使用GridFS,刪除數據
*
* @memberOf WOrmMongodb
* @param {String} id 輸入刪除id字串
* @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
*/
async function delGfs(id) {
let isErr = false
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
//bucket
let bucket = new mongodb.GridFSBucket(database, {
chunkSizeBytes: 10 * 1024 * 1024, //10mb
bucketName: opt.cl
})
//_findGfs
res = await _findGfs({ filename: id }, bucket)
//check
if (res.length === 0) {
res = `can not find id[${id}]`
}
else if (res.length > 1) {
res = `duplicate id[${id}]`
}
else {
//bid, get _id from res[0]
let bid = res[0]._id
//delete
res = await bucket.delete(bid)
//res
res = {
n: 1,
nDeleted: 1,
ok: 1,
}
//emit
ee.emit('change', 'delGfs', null, res)
}
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
async function _delGfs(bid, bucket) {
let isErr = false
//res
let res = null
try {
//delete
res = await bucket.delete(bid)
//res
res = {
n: 1,
nDeleted: 1,
ok: 1,
}
}
catch (err) {
isErr = true
res = err
}
finally {
// await client.close()
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 使用GridFS,刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
*
* @memberOf WOrmMongodb
* @param {Object} [find={}] 輸入刪除條件物件
* @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
*/
async function delAllGfs(find = {}) {
let isErr = false
//client
// let client = await MongoClient.connect(opt.url, optMGConn)
// let client = new MongoClient(opt.url, optMGConn)
let client = new MongoClient(opt.url)
//res
let res = null
try {
//database, collection
let database = client.db(opt.db)
//bucket
let bucket = new mongodb.GridFSBucket(database, {
chunkSizeBytes: 10 * 1024 * 1024, //10mb
bucketName: opt.cl
})
//_findGfs
res = await _findGfs(find, bucket)
//n
let n = size(res)
//ps
let ps = map(res, function(v) {
let bid = v._id
return _delGfs(bid, bucket)
})
//all
res = await Promise.all(ps)
//res
res = {
n,
ok: 1
}
//emit
ee.emit('change', 'delAllGfs', null, res)
}
catch (err) {
isErr = true
res = err
}
finally {
await client.close()
client = null
}
if (isErr) {
return Promise.reject(res)
}
return res
}
//bind
ee.select = select
ee.insert = insert
ee.save = save
ee.del = del
ee.delAll = delAll
ee.selectGfs = selectGfs
ee.insertGfs = insertGfs
ee.delGfs = delGfs
ee.delAllGfs = delAllGfs
return ee
}
export default WOrmMongodb