w-orm-mongodb 1.1.37 → 1.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,7 +29,7 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmMongodb.html">WOrmMongodb</a><ul class='methods'><li data-type='method'><a href="WOrmMongodb.html#.del">del</a></li><li data-type='method'><a href="WOrmMongodb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmMongodb.html#.delAllGfs">delAllGfs</a></li><li data-type='method'><a href="WOrmMongodb.html#.delGfs">delGfs</a></li><li data-type='method'><a href="WOrmMongodb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmMongodb.html#.insertGfs">insertGfs</a></li><li data-type='method'><a href="WOrmMongodb.html#.save">save</a></li><li data-type='method'><a href="WOrmMongodb.html#.select">select</a></li><li data-type='method'><a href="WOrmMongodb.html#.selectGfs">selectGfs</a></li></ul></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmMongodb.html">WOrmMongodb</a><ul class='methods'><li data-type='method'><a href="WOrmMongodb.html#.del">del</a></li><li data-type='method'><a href="WOrmMongodb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmMongodb.html#.delAllGfs">delAllGfs</a></li><li data-type='method'><a href="WOrmMongodb.html#.delGfs">delGfs</a></li><li data-type='method'><a href="WOrmMongodb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmMongodb.html#.insertGfs">insertGfs</a></li><li data-type='method'><a href="WOrmMongodb.html#.save">save</a></li><li data-type='method'><a href="WOrmMongodb.html#.select">select</a></li><li data-type='method'><a href="WOrmMongodb.html#.selectById">selectById</a></li><li data-type='method'><a href="WOrmMongodb.html#.selectByIdGfs">selectByIdGfs</a></li></ul></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -45,833 +45,1264 @@
45
45
 
46
46
  <section>
47
47
  <article>
48
- <pre class="prettyprint source linenums"><code>import events from 'events'
49
- import mongodb from 'mongodb'
50
- import stream from 'stream'
51
- import cloneDeep from 'lodash-es/cloneDeep.js'
52
- import get from 'lodash-es/get.js'
53
- import map from 'lodash-es/map.js'
54
- import omit from 'lodash-es/omit.js'
55
- import size from 'lodash-es/size.js'
56
- import genPm from 'wsemi/src/genPm.mjs'
57
- import genID from 'wsemi/src/genID.mjs'
58
- import isestr from 'wsemi/src/isestr.mjs'
59
- import isarr from 'wsemi/src/isarr.mjs'
60
- import isearr from 'wsemi/src/isearr.mjs'
61
- import iseobj from 'wsemi/src/iseobj.mjs'
62
- import pmSeries from 'wsemi/src/pmSeries.mjs'
63
-
64
-
65
- // //optMGConn
66
- // let optMGConn = {
67
- // useNewUrlParser: true,
68
- // useUnifiedTopology: true
69
- // }
70
-
71
-
72
- /**
73
- * 操作資料庫(MongoDB)
74
- *
75
- * @class
76
- * @param {Object} [opt={}] 輸入設定物件,預設{}
77
- * @param {String} [opt.url='mongodb://127.0.0.1:27017'] 輸入連接資料庫字串,預設'mongodb://127.0.0.1:27017'
78
- * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
79
- * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
80
- * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
81
- */
82
- function WOrmMongodb(opt = {}) {
83
-
84
-
85
- //default
86
- if (!opt.url) {
87
- opt.url = 'mongodb://127.0.0.1:27017'
88
- }
89
- if (!opt.db) {
90
- opt.db = 'worm'
91
- }
92
- if (!opt.cl) {
93
- opt.cl = 'test'
94
- }
95
-
96
-
97
- //ee
98
- let ee = new events.EventEmitter()
99
-
100
-
101
- //MongoClient
102
- let MongoClient = mongodb.MongoClient
103
-
104
-
105
- /**
106
- * 查詢數據
107
- *
108
- * @memberOf WOrmMongodb
109
- * @param {Object} [find={}] 輸入查詢條件物件
110
- * @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
111
- */
112
- async function select(find = {}) {
113
- let isErr = false
114
-
115
- //client
116
- // let client = await MongoClient.connect(opt.url, optMGConn)
117
- // let client = new MongoClient(opt.url, optMGConn)
118
- let client = new MongoClient(opt.url)
119
-
120
- //res
121
- let res = null
122
- try {
123
-
124
- //database, collection
125
- let database = client.db(opt.db)
126
- let collection = database.collection(opt.cl)
127
-
128
- //find
129
- let cursor = collection.find(find)
130
- //.sort({ $natural: -1 })
131
- //.limit(N)
132
-
133
- //toArray
134
- res = await cursor.toArray()
135
-
136
- //omit
137
- res = map(res, function(v) {
138
- v = omit(v, '_id')
139
- return v
140
- })
141
-
142
- }
143
- catch (err) {
144
- isErr = true
145
- res = err
146
- }
147
- finally {
148
- await client.close()
149
- client = null
150
- }
151
-
152
- if (isErr) {
153
- return Promise.reject(res)
154
- }
155
- return res
156
- }
157
-
158
-
159
- /**
160
- * 插入數據,插入同樣數據會自動產生不同_id,故insert前需自行判斷有無重複
161
- *
162
- * @memberOf WOrmMongodb
163
- * @param {Object|Array} data 輸入數據物件或陣列
164
- * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
165
- */
166
- async function insert(data) {
167
- let isErr = false
168
-
169
- //check
170
- if (!iseobj(data) &amp;&amp; !isearr(data)) {
171
- return {
172
- n: 0,
173
- nInserted: 0,
174
- ok: 1,
175
- }
176
- }
177
-
178
- //cloneDeep
179
- data = cloneDeep(data)
180
-
181
- //client
182
- // let client = await MongoClient.connect(opt.url, optMGConn)
183
- // let client = new MongoClient(opt.url, optMGConn)
184
- let client = new MongoClient(opt.url)
185
-
186
- //res
187
- let res = null
188
- try {
189
-
190
- //database, collection
191
- let database = client.db(opt.db)
192
- let collection = database.collection(opt.cl)
193
-
194
- //check
195
- if (!isarr(data)) {
196
- data = [data]
197
- }
198
-
199
- //check id
200
- data = map(data, function(v) {
201
- if (!isestr(v.id)) {
202
- v.id = genID()
203
- }
204
- return v
205
- })
206
-
207
- //insertMany
208
- res = await collection.insertMany(data)
209
-
210
- //check
211
- if (res.insertedCount > 0) {
212
-
213
- //res
214
- res = {
215
- n: size(data),
216
- nInserted: res.insertedCount,
217
- ok: res.acknowledged ? 1 : 0,
218
- }
219
-
220
- //emit
221
- ee.emit('change', 'insert', data, res)
222
-
223
- }
224
- else {
225
- isErr = true
226
-
227
- //res
228
- res = `no insert data`
229
-
230
- }
231
-
232
- }
233
- catch (err) {
234
- isErr = true
235
- res = err
236
- }
237
- finally {
238
- await client.close()
239
- client = null
240
- }
241
-
242
- if (isErr) {
243
- return Promise.reject(res)
244
- }
245
- return res
246
- }
247
-
248
-
249
- /**
250
- * 儲存數據
251
- *
252
- * @memberOf WOrmMongodb
253
- * @param {Object|Array} data 輸入數據物件或陣列
254
- * @param {Object} [option={}] 輸入設定物件,預設為{}
255
- * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
256
- * @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
257
- */
258
- async function save(data, option = {}) {
259
- let isErr = false
260
-
261
- //check
262
- if (!iseobj(data) &amp;&amp; !isearr(data)) {
263
- return []
264
- }
265
-
266
- //cloneDeep
267
- data = cloneDeep(data)
268
-
269
- //autoInsert
270
- let autoInsert = get(option, 'autoInsert', true)
271
-
272
- //client
273
- // let client = await MongoClient.connect(opt.url, optMGConn)
274
- // let client = new MongoClient(opt.url, optMGConn)
275
- let client = new MongoClient(opt.url)
276
-
277
- //res
278
- let res = null
279
- try {
280
-
281
- //database, collection
282
- let database = client.db(opt.db)
283
- let collection = database.collection(opt.cl)
284
-
285
- //check
286
- if (!isarr(data)) {
287
- data = [data]
288
- }
289
-
290
- //check id
291
- data = map(data, function(v) {
292
- if (!isestr(v.id)) {
293
- v.id = genID()
294
- }
295
- return v
296
- })
297
-
298
- //pmSeries
299
- res = await pmSeries(data, async(v) => {
300
-
301
- //rest
302
- let rest = null
303
-
304
- //oper
305
- rest = await collection.findOneAndUpdate({ id: v.id }, { $set: v })
306
- // console.log('rest', rest)
307
-
308
- if (iseobj(rest)) {
309
- rest = {
310
- n: 1,
311
- nModified: 1,
312
- ok: 1,
313
- }
314
- }
315
- else {
316
- rest = {
317
- n: 0,
318
- nModified: 0,
319
- ok: 1,
320
- }
321
- }
322
-
323
- //autoInsert
324
- if (autoInsert &amp;&amp; rest.n === 0) {
325
- rest = await insert(v)
326
- }
327
-
328
- return rest
329
- })
330
-
331
- //emit
332
- ee.emit('change', 'save', data, res)
333
-
334
- }
335
- catch (err) {
336
- isErr = true
337
- res = err
338
- }
339
- finally {
340
- await client.close()
341
- client = null
342
- }
343
-
344
- if (isErr) {
345
- return Promise.reject(res)
346
- }
347
- return res
348
- }
349
-
350
-
351
- /**
352
- * 刪除數據
353
- *
354
- * @memberOf WOrmMongodb
355
- * @param {Object|Array} data 輸入數據物件或陣列
356
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
357
- */
358
- async function del(data) {
359
- let isErr = false
360
-
361
- //check
362
- if (!iseobj(data) &amp;&amp; !isearr(data)) {
363
- return []
364
- }
365
-
366
- //cloneDeep
367
- data = cloneDeep(data)
368
-
369
- //client
370
- // let client = await MongoClient.connect(opt.url, optMGConn)
371
- // let client = new MongoClient(opt.url, optMGConn)
372
- let client = new MongoClient(opt.url)
373
-
374
- //res
375
- let res = null
376
- try {
377
-
378
- //database, collection
379
- let database = client.db(opt.db)
380
- let collection = database.collection(opt.cl)
381
-
382
- //check
383
- if (!isarr(data)) {
384
- data = [data]
385
- }
386
-
387
- //pmSeries
388
- res = await pmSeries(data, async(v) => {
389
-
390
- //rest
391
- let rest = null
392
-
393
- //deleteOne
394
- rest = await collection.deleteOne({ id: v.id })
395
-
396
- //rest
397
- rest = {
398
- n: rest.deletedCount,
399
- nDeleted: rest.deletedCount,
400
- ok: rest.acknowledged ? 1 : 0,
401
- }
402
-
403
- return rest
404
- })
405
-
406
- //emit
407
- ee.emit('change', 'del', data, res)
408
-
409
- }
410
- catch (err) {
411
- isErr = true
412
- res = err
413
- }
414
- finally {
415
- await client.close()
416
- client = null
417
- }
418
-
419
- if (isErr) {
420
- return Promise.reject(res)
421
- }
422
- return res
423
- }
424
-
425
-
426
- /**
427
- * 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
428
- *
429
- * @memberOf WOrmMongodb
430
- * @param {Object} [find={}] 輸入刪除條件物件
431
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
432
- */
433
- async function delAll(find = {}) {
434
- let isErr = false
435
-
436
- //client
437
- // let client = await MongoClient.connect(opt.url, optMGConn)
438
- // let client = new MongoClient(opt.url, optMGConn)
439
- let client = new MongoClient(opt.url)
440
-
441
- //res
442
- let res = null
443
- try {
444
-
445
- //database, collection
446
- let database = client.db(opt.db)
447
- let collection = database.collection(opt.cl)
448
-
449
- //deleteMany
450
- res = await collection.deleteMany(find)
451
-
452
- //res
453
- res = {
454
- n: res.deletedCount,
455
- nDeleted: res.deletedCount,
456
- ok: res.acknowledged ? 1 : 0,
457
- }
458
-
459
- //emit
460
- ee.emit('change', 'delAll', null, res)
461
-
462
- }
463
- catch (err) {
464
- isErr = true
465
- res = err
466
- }
467
- finally {
468
- await client.close()
469
- client = null
470
- }
471
-
472
-
473
- if (isErr) {
474
- return Promise.reject(res)
475
- }
476
- return res
477
- }
478
-
479
-
480
- /**
481
- * 使用GridFS,插入數據,需為Uint8Array格式
482
- *
483
- * @memberOf WOrmMongodb
484
- * @param {Uint8Array} u8a
485
- * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
486
- */
487
- async function insertGfs(u8a) {
488
- let isErr = false
489
-
490
- //id
491
- let id = genID()
492
-
493
- //buf
494
- let buf = Buffer.from(u8a)
495
-
496
- //client
497
- // let client = await MongoClient.connect(opt.url, optMGConn)
498
- // let client = new MongoClient(opt.url, optMGConn)
499
- let client = new MongoClient(opt.url)
500
-
501
- //core
502
- let core = async (id, buf) => {
503
-
504
- //pm
505
- let pm = genPm()
506
-
507
- //database
508
- let database = client.db(opt.db)
509
-
510
- //bucket
511
- let bucket = new mongodb.GridFSBucket(database, {
512
- chunkSizeBytes: 10 * 1024 * 1024, //10mb
513
- bucketName: opt.cl
514
- })
515
-
516
- //stream
517
- let sm = new stream.Readable()
518
- sm._read = () => {}
519
- sm.push(buf)
520
- sm.push(null)
521
- sm.pipe(bucket.openUploadStream(id)) //pipe是接bucket的Writable, 所以會監聽finish
522
- .on('error', function(err) {
523
-
524
- //reject
525
- pm.reject(err)
526
-
527
- })
528
- .on('finish', function() {
529
-
530
- //res
531
- let res = { n: 1, ok: 1, id }
532
-
533
- //resolve
534
- pm.resolve(res)
535
-
536
- })
537
-
538
- return pm
539
- }
540
-
541
- //res
542
- let res = null
543
- try {
544
-
545
- //core
546
- res = await core(id, buf)
547
-
548
- //emit
549
- ee.emit('change', 'insertGfs', null, res)
550
-
551
- }
552
- catch (err) {
553
- isErr = true
554
- res = err
555
- }
556
- finally {
557
- await client.close()
558
- client = null
559
- }
560
-
561
- if (isErr) {
562
- return Promise.reject(res)
563
- }
564
- return res
565
- }
566
-
567
-
568
- /**
569
- * 使用GridFS,查詢數據
570
- *
571
- * @memberOf WOrmMongodb
572
- * @param {String} id 輸入查詢id字串
573
- * @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
574
- */
575
- async function selectGfs(id) {
576
- let isErr = false
577
-
578
- //client
579
- // let client = await MongoClient.connect(opt.url, optMGConn)
580
- // let client = new MongoClient(opt.url, optMGConn)
581
- let client = new MongoClient(opt.url)
582
-
583
- //core
584
- let core = async (id) => {
585
-
586
- //pm
587
- let pm = genPm()
588
-
589
- //database
590
- let database = client.db(opt.db)
591
-
592
- //bucket
593
- let bucket = new mongodb.GridFSBucket(database, {
594
- chunkSizeBytes: 10 * 1024 * 1024, //10mb
595
- bucketName: opt.cl
596
- })
597
-
598
- //buf
599
- let buf = Buffer.from('')
600
-
601
- //stream
602
- let sm = bucket.openDownloadStreamByName(id)
603
- sm.on('data', function (chunk) {
604
- buf = Buffer.concat([buf, chunk])
605
- })
606
- sm.on('error', function (err) {
607
-
608
- //reject
609
- pm.reject(err)
610
-
611
- })
612
- sm.on('end', function () {
613
-
614
- //u8a
615
- let u8a = new Uint8Array(buf)
616
-
617
- //clean memory
618
- buf = null
619
-
620
- //resolve
621
- pm.resolve(u8a)
622
-
623
- })
624
-
625
- return pm
626
- }
627
-
628
- //res
629
- let res = null
630
- try {
631
-
632
- //core
633
- res = await core(id)
634
-
635
- }
636
- catch (err) {
637
- isErr = true
638
- res = err
639
- }
640
- finally {
641
- await client.close()
642
- client = null
643
- }
644
-
645
- if (isErr) {
646
- return Promise.reject(res)
647
- }
648
- return res
649
- }
650
-
651
-
652
- async function _findGfs(find = {}, bucket) {
653
- let isErr = false
654
-
655
- //res
656
- let res = null
657
- try {
658
-
659
- //find
660
- let cursor = bucket.find(find)
661
-
662
- //toArray
663
- res = await cursor.toArray()
664
-
665
- }
666
- catch (err) {
667
- isErr = true
668
- res = err
669
- }
670
- finally {
671
- // await client.close()
672
- }
673
-
674
- if (isErr) {
675
- return Promise.reject(res)
676
- }
677
- return res
678
- }
679
-
680
-
681
- /**
682
- * 使用GridFS,刪除數據
683
- *
684
- * @memberOf WOrmMongodb
685
- * @param {String} id 輸入刪除id字串
686
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
687
- */
688
- async function delGfs(id) {
689
- let isErr = false
690
-
691
- //client
692
- // let client = await MongoClient.connect(opt.url, optMGConn)
693
- // let client = new MongoClient(opt.url, optMGConn)
694
- let client = new MongoClient(opt.url)
695
-
696
- //res
697
- let res = null
698
- try {
699
-
700
- //database, collection
701
- let database = client.db(opt.db)
702
-
703
- //bucket
704
- let bucket = new mongodb.GridFSBucket(database, {
705
- chunkSizeBytes: 10 * 1024 * 1024, //10mb
706
- bucketName: opt.cl
707
- })
708
-
709
- //_findGfs
710
- res = await _findGfs({ filename: id }, bucket)
711
-
712
- //check
713
- if (res.length === 0) {
714
- res = `can not find id[${id}]`
715
- }
716
- else if (res.length > 1) {
717
- res = `duplicate id[${id}]`
718
- }
719
- else {
720
-
721
- //bid, get _id from res[0]
722
- let bid = res[0]._id
723
-
724
- //delete
725
- res = await bucket.delete(bid)
726
-
727
- //res
728
- res = {
729
- n: 1,
730
- nDeleted: 1,
731
- ok: 1,
732
- }
733
-
734
- //emit
735
- ee.emit('change', 'delGfs', null, res)
736
-
737
- }
738
-
739
- }
740
- catch (err) {
741
- isErr = true
742
- res = err
743
- }
744
- finally {
745
- await client.close()
746
- client = null
747
- }
748
-
749
- if (isErr) {
750
- return Promise.reject(res)
751
- }
752
- return res
753
- }
754
-
755
-
756
- async function _delGfs(bid, bucket) {
757
- let isErr = false
758
-
759
- //res
760
- let res = null
761
- try {
762
-
763
- //delete
764
- res = await bucket.delete(bid)
765
-
766
- //res
767
- res = {
768
- n: 1,
769
- nDeleted: 1,
770
- ok: 1,
771
- }
772
-
773
- }
774
- catch (err) {
775
- isErr = true
776
- res = err
777
- }
778
- finally {
779
- // await client.close()
780
- }
781
-
782
- if (isErr) {
783
- return Promise.reject(res)
784
- }
785
- return res
786
- }
787
-
788
-
789
- /**
790
- * 使用GridFS,刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
791
- *
792
- * @memberOf WOrmMongodb
793
- * @param {Object} [find={}] 輸入刪除條件物件
794
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
795
- */
796
- async function delAllGfs(find = {}) {
797
- let isErr = false
798
-
799
- //client
800
- // let client = await MongoClient.connect(opt.url, optMGConn)
801
- // let client = new MongoClient(opt.url, optMGConn)
802
- let client = new MongoClient(opt.url)
803
-
804
- //res
805
- let res = null
806
- try {
807
-
808
- //database, collection
809
- let database = client.db(opt.db)
810
-
811
- //bucket
812
- let bucket = new mongodb.GridFSBucket(database, {
813
- chunkSizeBytes: 10 * 1024 * 1024, //10mb
814
- bucketName: opt.cl
815
- })
816
-
817
- //_findGfs
818
- res = await _findGfs(find, bucket)
819
-
820
- //n
821
- let n = size(res)
822
-
823
- //ps
824
- let ps = map(res, function(v) {
825
- let bid = v._id
826
- return _delGfs(bid, bucket)
827
- })
828
-
829
- //all
830
- res = await Promise.all(ps)
831
-
832
- //res
833
- res = {
834
- n,
835
- ok: 1
836
- }
837
-
838
- //emit
839
- ee.emit('change', 'delAllGfs', null, res)
840
-
841
- }
842
- catch (err) {
843
- isErr = true
844
- res = err
845
- }
846
- finally {
847
- await client.close()
848
- client = null
849
- }
850
-
851
- if (isErr) {
852
- return Promise.reject(res)
853
- }
854
- return res
855
- }
856
-
857
-
858
- //bind
859
- ee.select = select
860
- ee.insert = insert
861
- ee.save = save
862
- ee.del = del
863
- ee.delAll = delAll
864
- ee.selectGfs = selectGfs
865
- ee.insertGfs = insertGfs
866
- ee.delGfs = delGfs
867
- ee.delAllGfs = delAllGfs
868
-
869
-
870
- return ee
871
- }
872
-
873
-
874
- export default WOrmMongodb
48
+ <pre class="prettyprint source linenums"><code>import events from 'events'
49
+ import mongodb from 'mongodb'
50
+ import stream from 'stream'
51
+ import cloneDeep from 'lodash-es/cloneDeep.js'
52
+ import every from 'lodash-es/every.js'
53
+ import get from 'lodash-es/get.js'
54
+ import map from 'lodash-es/map.js'
55
+ import omit from 'lodash-es/omit.js'
56
+ import size from 'lodash-es/size.js'
57
+ import genPm from 'wsemi/src/genPm.mjs'
58
+ import genID from 'wsemi/src/genID.mjs'
59
+ import isestr from 'wsemi/src/isestr.mjs'
60
+ import isarr from 'wsemi/src/isarr.mjs'
61
+ import isearr from 'wsemi/src/isearr.mjs'
62
+ import iseobj from 'wsemi/src/iseobj.mjs'
63
+ import isu8arr from 'wsemi/src/isu8arr.mjs'
64
+ import pmSeries from 'wsemi/src/pmSeries.mjs'
65
+
66
+
67
+ /**
68
+ * 操作資料庫(MongoDB)
69
+ *
70
+ * @class
71
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
72
+ * @param {String} [opt.url='mongodb://127.0.0.1:27017'] 輸入連接資料庫字串,預設'mongodb://127.0.0.1:27017'
73
+ * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
74
+ * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
75
+ * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
76
+ */
77
+ function WOrmMongodb(opt = {}) {
78
+
79
+
80
+ //default
81
+ if (!opt.url) {
82
+ opt.url = 'mongodb://127.0.0.1:27017'
83
+ }
84
+ if (!opt.db) {
85
+ opt.db = 'worm'
86
+ }
87
+ if (!opt.cl) {
88
+ opt.cl = 'test'
89
+ }
90
+
91
+
92
+ //_indexReady, 唯一索引只須建立一次, 以旗標記錄避免每次操作皆多一次round-trip
93
+ let _indexReady = false
94
+
95
+
96
+ //_indexGfsReady, GridFS之唯一索引亦只須建立一次
97
+ let _indexGfsReady = false
98
+
99
+
100
+ //ee
101
+ let ee = new events.EventEmitter()
102
+
103
+
104
+ //MongoClient
105
+ let MongoClient = mongodb.MongoClient
106
+
107
+
108
+ /**
109
+ * 取錯誤訊息字串,供逐筆結果之err欄位使用
110
+ *
111
+ * @ignore
112
+ * @param {Error|String} err 輸入錯誤物件或字串
113
+ * @returns {String} 回傳錯誤訊息字串
114
+ */
115
+ function genErrMsg(err) {
116
+
117
+ //message
118
+ let message = get(err, 'message')
119
+ if (isestr(message)) {
120
+ return message
121
+ }
122
+
123
+ return String(err)
124
+ }
125
+
126
+
127
+ /**
128
+ * 發出change事件,訂閱函數拋錯不得影響本次操作之結果,故另包try並自行吞掉
129
+ *
130
+ * @ignore
131
+ * @param {String} mode 輸入操作模式字串
132
+ * @param {Array|null} data 輸入本次操作之數據
133
+ * @param {Object|Array} res 輸入本次操作之結果
134
+ * @returns {undefined} 無回傳值
135
+ */
136
+ function emitChange(mode, data, res) {
137
+ try {
138
+ ee.emit('change', mode, data, res)
139
+ }
140
+ catch (err) {
141
+ console.log(err)
142
+ }
143
+ }
144
+
145
+
146
+ /**
147
+ * 判定是否為唯一索引重複鍵錯誤(11000),批次插入時須全部寫入錯誤皆為重複鍵才算
148
+ *
149
+ * @ignore
150
+ * @param {Error} err 輸入錯誤物件
151
+ * @returns {Boolean} 回傳是否為重複鍵錯誤布林值
152
+ */
153
+ function isDupKeyError(err) {
154
+
155
+ //writeErrors, 批次寫入時各筆錯誤置於writeErrors內
156
+ let writeErrors = get(err, 'writeErrors')
157
+ if (isearr(writeErrors)) {
158
+ return every(writeErrors, function(v) {
159
+ return get(v, 'code') === 11000 || get(v, 'err.code') === 11000
160
+ })
161
+ }
162
+
163
+ return get(err, 'code') === 11000
164
+ }
165
+
166
+
167
+ /**
168
+ * 於id欄位建立唯一索引,令[檢查id是否存在]與[寫入]得由MongoDB於單一文件操作內原子完成
169
+ * 註: 若既有資料表內已存在重複id,建立索引會失敗並將錯誤往外拋,須先自行清除重複數據,
170
+ * 此錯誤影響本次操作之全部數據,屬整批性錯誤,故不轉為逐筆之ok為0
171
+ *
172
+ * @ignore
173
+ * @param {Object} collection 輸入資料表物件
174
+ * @returns {Promise} 回傳Promise,resolve回傳undefined,reject回傳錯誤訊息
175
+ */
176
+ async function ensureIndex(collection) {
177
+
178
+ //check
179
+ if (_indexReady) {
180
+ return
181
+ }
182
+
183
+ //createIndex, 已存在同樣索引時MongoDB不會重建亦不報錯
184
+ await collection.createIndex({ id: 1 }, { unique: true })
185
+
186
+ //_indexReady
187
+ _indexReady = true
188
+
189
+ }
190
+
191
+
192
+ /**
193
+ * 使用GridFS,於filename欄位建立唯一索引,令[檢查id是否存在]與[寫入]由MongoDB判定而非由本套件先讀後寫
194
+ * 註: GridFS以files資料表之filename存放id,其本身不具唯一性,須另建唯一索引方能達成[已存在則跳過];
195
+ * 若既有資料表內已存在重複id,建立索引會失敗並將錯誤往外拋,須先自行清除重複數據
196
+ *
197
+ * @ignore
198
+ * @param {Object} database 輸入資料庫物件
199
+ * @returns {Promise} 回傳Promise,resolve回傳undefined,reject回傳錯誤訊息
200
+ */
201
+ async function ensureIndexGfs(database) {
202
+
203
+ //check
204
+ if (_indexGfsReady) {
205
+ return
206
+ }
207
+
208
+ //createIndex, 已存在同樣索引時MongoDB不會重建亦不報錯
209
+ await database.collection(`${opt.cl}.files`).createIndex({ filename: 1 }, { unique: true })
210
+
211
+ //_indexGfsReady
212
+ _indexGfsReady = true
213
+
214
+ }
215
+
216
+
217
+ /**
218
+ * 使用GridFS,上傳單筆數據
219
+ * 註: 已存在同一id時MongoDB以重複鍵錯誤拒絕,惟chunks於files文件之前即已寫入,
220
+ * 故須以files_id清除該次所遺留之chunks,避免累積孤兒數據
221
+ *
222
+ * @ignore
223
+ * @param {Object} bucket 輸入GridFSBucket物件
224
+ * @param {Object} clChunks 輸入GridFS之chunks資料表物件
225
+ * @param {String} id 輸入id字串
226
+ * @param {Uint8Array} u8a 輸入數據Uint8Array
227
+ * @returns {Promise} 回傳Promise,resolve回傳本次是否已插入布林值,reject回傳錯誤訊息
228
+ */
229
+ function _uploadGfs(bucket, clChunks, id, u8a) {
230
+
231
+ //pm
232
+ let pm = genPm()
233
+
234
+ //ws, files_id於finish前即可取得, 供清除孤兒chunks用
235
+ let ws = bucket.openUploadStream(id)
236
+ let fid = ws.id
237
+
238
+ //stream
239
+ let sm = new stream.Readable()
240
+ sm._read = () => {}
241
+ sm.push(Buffer.from(u8a))
242
+ sm.push(null)
243
+ sm.pipe(ws) //pipe是接bucket的Writable, 所以會監聽finish
244
+ .on('error', function(err) {
245
+
246
+ //check, 重複鍵錯誤表示已存在同一id而跳過, 屬正常結果而非錯誤
247
+ if (!isDupKeyError(err)) {
248
+ pm.reject(err)
249
+ return
250
+ }
251
+
252
+ //清除本次所遺留之孤兒chunks
253
+ clChunks.deleteMany({ files_id: fid })
254
+ .then(function() {
255
+ pm.resolve(false)
256
+ })
257
+ .catch(function(err) {
258
+ pm.reject(err)
259
+ })
260
+
261
+ })
262
+ .on('finish', function() {
263
+
264
+ //resolve
265
+ pm.resolve(true)
266
+
267
+ })
268
+
269
+ return pm
270
+ }
271
+
272
+
273
+ /**
274
+ * 查詢數據
275
+ *
276
+ * @memberOf WOrmMongodb
277
+ * @param {Object} [find={}] 輸入查詢條件物件
278
+ * @returns {Promise} 回傳Promise,resolve回傳數據陣列,無符合數據回傳空陣列,reject回傳錯誤訊息
279
+ */
280
+ async function select(find = {}) {
281
+ let isErr = false
282
+
283
+ //client
284
+ let client = new MongoClient(opt.url)
285
+
286
+ //res
287
+ let res = null
288
+ try {
289
+
290
+ //database, collection
291
+ let database = client.db(opt.db)
292
+ let collection = database.collection(opt.cl)
293
+
294
+ //find
295
+ let cursor = collection.find(find)
296
+
297
+ //toArray
298
+ res = await cursor.toArray()
299
+
300
+ //omit, 去除資料庫內部欄位_id, 令回傳欄位與寫入時所給者一致
301
+ res = map(res, function(v) {
302
+ v = omit(v, '_id')
303
+ return v
304
+ })
305
+
306
+ }
307
+ catch (err) {
308
+ isErr = true
309
+ res = err
310
+ }
311
+ finally {
312
+ await client.close()
313
+ client = null
314
+ }
315
+
316
+ if (isErr) {
317
+ return Promise.reject(res)
318
+ }
319
+ return res
320
+ }
321
+
322
+
323
+ /**
324
+ * 由id查詢單筆數據,因由MongoDB查找且僅回傳單筆,不需如select提取全部符合數據再處理,故數據量大時效能較佳
325
+ * 註: 本函數不得有副作用,故不建立唯一索引
326
+ *
327
+ * @memberOf WOrmMongodb
328
+ * @param {String} id 輸入id字串
329
+ * @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此id或id無效則回傳null,reject回傳錯誤訊息
330
+ */
331
+ async function selectById(id) {
332
+ let isErr = false
333
+
334
+ //check
335
+ if (!isestr(id)) {
336
+ //未給有效id視為查無數據, 判定基準與insert、save、del內對id之認定一致
337
+ return null
338
+ }
339
+
340
+ //client
341
+ let client = new MongoClient(opt.url)
342
+
343
+ //res
344
+ let res = null
345
+ try {
346
+
347
+ //database, collection
348
+ let database = client.db(opt.db)
349
+ let collection = database.collection(opt.cl)
350
+
351
+ //findOne, 以投影去除_id, 令回傳形狀與select一致
352
+ let v = await collection.findOne({ id }, { projection: { _id: 0 } })
353
+
354
+ //check, 判定基準與insert、save、del內對既有數據之認定一致
355
+ if (iseobj(v)) {
356
+ res = v
357
+ }
358
+ else {
359
+ //不存在id, 回傳null
360
+ res = null
361
+ }
362
+
363
+ }
364
+ catch (err) {
365
+ isErr = true
366
+ res = err
367
+ }
368
+ finally {
369
+ await client.close()
370
+ client = null
371
+ }
372
+
373
+ if (isErr) {
374
+ return Promise.reject(res)
375
+ }
376
+ return res
377
+ }
378
+
379
+
380
+ /**
381
+ * 插入數據,僅於id不存在時寫入,已存在者跳過且不覆寫
382
+ * 由MongoDB於唯一索引上原子完成[檢查id未存在]與[寫入],併發時同一id僅有一次成功
383
+ * 註: n為輸入筆數,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
384
+ *
385
+ * @memberOf WOrmMongodb
386
+ * @param {Object|Array} data 輸入數據物件或陣列
387
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
388
+ */
389
+ async function insert(data) {
390
+ let isErr = false
391
+
392
+ //check
393
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
394
+ return {
395
+ n: 0,
396
+ nInserted: 0,
397
+ ok: 1,
398
+ }
399
+ }
400
+
401
+ //cloneDeep
402
+ data = cloneDeep(data)
403
+
404
+ //client
405
+ let client = new MongoClient(opt.url)
406
+
407
+ //res
408
+ let res = null
409
+ try {
410
+
411
+ //connect, 令連線失敗於寫入前即拋出, 屬影響全部數據之整批性錯誤
412
+ await client.connect()
413
+
414
+ //database, collection
415
+ let database = client.db(opt.db)
416
+ let collection = database.collection(opt.cl)
417
+
418
+ //check
419
+ if (!isarr(data)) {
420
+ data = [data]
421
+ }
422
+
423
+ //check id
424
+ data = map(data, function(v) {
425
+ if (!isestr(v.id)) {
426
+ v.id = genID()
427
+ }
428
+ return v
429
+ })
430
+
431
+ //ensureIndex
432
+ await ensureIndex(collection)
433
+
434
+ //nAll, n之基準為輸入筆數
435
+ let nAll = size(data)
436
+
437
+ //insertMany, ordered:false令已存在id者跳過而不中斷整批插入,
438
+ //同批含重複id時亦僅首筆成功, 故不須逐筆插入即可取得實際插入筆數
439
+ let nInserted = 0
440
+ try {
441
+ let r = await collection.insertMany(data, { ordered: false })
442
+ nInserted = r.insertedCount
443
+ }
444
+ catch (err) {
445
+
446
+ //僅重複鍵錯誤可視為[已存在id而跳過], 其餘錯誤影響全部數據, 須往外拋
447
+ if (!isDupKeyError(err)) {
448
+ throw err
449
+ }
450
+
451
+ //重複鍵錯誤時仍可由result取得實際插入筆數
452
+ nInserted = get(err, 'result.insertedCount', 0)
453
+
454
+ }
455
+
456
+ //res, 全數已存在而nInserted為0屬正常結果, 不視為錯誤
457
+ res = {
458
+ n: nAll,
459
+ nInserted,
460
+ ok: 1,
461
+ }
462
+
463
+ }
464
+ catch (err) {
465
+ isErr = true
466
+ res = err
467
+ }
468
+ finally {
469
+ await client.close()
470
+ client = null
471
+ }
472
+
473
+ //emit
474
+ if (!isErr) {
475
+ emitChange('insert', data, res)
476
+ }
477
+
478
+ if (isErr) {
479
+ return Promise.reject(res)
480
+ }
481
+ return res
482
+ }
483
+
484
+
485
+ /**
486
+ * 儲存單筆數據,由MongoDB於單一updateOne內原子完成[查找id]與[更新或插入]
487
+ * 註: MongoDB單一文件操作本即為原子,故不須開啟transaction(transaction另須replica set,standalone不支援)
488
+ *
489
+ * @ignore
490
+ * @param {Object} collection 輸入資料表物件
491
+ * @param {Object} v 輸入數據物件
492
+ * @param {Boolean} autoInsert 輸入是否於查無數據時自動改以插入處理布林值
493
+ * @returns {Promise} 回傳Promise,resolve回傳本筆儲存結果,本筆失敗時亦resolve並以ok為0回報
494
+ */
495
+ async function saveOne(collection, v, autoInsert) {
496
+
497
+ //nTry, 併發upsert可能因他方同時插入同一id而拋重複鍵錯誤,
498
+ //此時該id已存在, 重試即會走更新路徑, 故給予有限次數重試令其收斂
499
+ let nTry = 3
500
+
501
+ //rest
502
+ let rest = null
503
+
504
+ //inserted, 供結果定案後發出insert事件用
505
+ let inserted = false
506
+
507
+ for (let i = 0; i &lt; nTry; i++) {
508
+
509
+ try {
510
+
511
+ //updateOne, upsert僅於autoInsert時開啟, 未開啟時查無數據即不寫入以免無中生有,
512
+ //由MongoDB於單一操作內原子完成[查找id]與[更新或插入],
513
+ //併發時不會有兩方各自讀到查無數據再各自插入而產生重複id, 亦不會有讀改寫之遺失更新
514
+ let r = await collection.updateOne({ id: v.id }, { $set: v }, {
515
+ upsert: autoInsert,
516
+ })
517
+
518
+ //matchedCount, modifiedCount, upsertedCount
519
+ let matchedCount = get(r, 'matchedCount', 0)
520
+ let modifiedCount = get(r, 'modifiedCount', 0)
521
+ let upsertedCount = get(r, 'upsertedCount', 0)
522
+
523
+ if (upsertedCount > 0) {
524
+ //原不存在而插入
525
+
526
+ //rest
527
+ rest = {
528
+ n: 1,
529
+ nInserted: 1,
530
+ nModified: 0,
531
+ ok: 1,
532
+ }
533
+
534
+ //inserted
535
+ inserted = true
536
+
537
+ }
538
+ else if (matchedCount > 0) {
539
+ //原已存在, 合併後內容有變更方為更新
540
+ //由MongoDB於伺服器端將待寫入物件合併進現值後與現值比對, 結果相同者不寫入而回modifiedCount為0,
541
+ //故僅給部份欄位且該些欄位值皆與現值相同時, 合併結果等於現值, nModified亦為0,
542
+ //係於同一原子操作內完成比對與寫入, 故不須另行預讀, 亦無預讀值過期之疑慮
543
+
544
+ //rest
545
+ rest = {
546
+ n: 1,
547
+ nInserted: 0,
548
+ nModified: modifiedCount > 0 ? 1 : 0,
549
+ ok: 1,
550
+ }
551
+
552
+ }
553
+ else {
554
+ //查無數據且未開啟autoInsert
555
+
556
+ //rest
557
+ rest = {
558
+ n: 0,
559
+ nInserted: 0,
560
+ nModified: 0,
561
+ ok: 1,
562
+ }
563
+
564
+ }
565
+
566
+ break
567
+ }
568
+ catch (err) {
569
+
570
+ //重複鍵錯誤且未達重試上限則重試
571
+ if (isDupKeyError(err) &amp;&amp; i &lt; nTry - 1) {
572
+ continue
573
+ }
574
+
575
+ //其餘視為本筆失敗, 不中斷整批, 由呼叫端以ok與err判讀
576
+
577
+ //rest
578
+ rest = {
579
+ n: 1,
580
+ nInserted: 0,
581
+ nModified: 0,
582
+ ok: 0,
583
+ err: genErrMsg(err),
584
+ }
585
+
586
+ break
587
+ }
588
+
589
+ }
590
+
591
+ //emit, 須於結果定案後發出, 避免訂閱函數拋錯影響本筆結果
592
+ if (inserted) {
593
+ emitChange('insert', [v], rest)
594
+ }
595
+
596
+ return rest
597
+ }
598
+
599
+
600
+ /**
601
+ * 儲存數據,以id為準更新既有數據,未給之欄位會保留;id不存在且option.autoInsert為true時改為插入
602
+ * 註: n為id命中筆數,命中或經插入而產生皆為1;[內容相同]之判定基準為將待儲存物件合併進現值後結果與現值相同,
603
+ * 相同者不寫入而nModified為0;本筆失敗不中斷整批,該筆以ok為0並附err回報
604
+ *
605
+ * @memberOf WOrmMongodb
606
+ * @param {Object|Array} data 輸入數據物件或陣列
607
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
608
+ * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
609
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之儲存結果陣列,reject回傳錯誤訊息
610
+ */
611
+ async function save(data, option = {}) {
612
+ let isErr = false
613
+
614
+ //check
615
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
616
+ return []
617
+ }
618
+
619
+ //cloneDeep
620
+ data = cloneDeep(data)
621
+
622
+ //autoInsert
623
+ let autoInsert = get(option, 'autoInsert', true)
624
+
625
+ //client
626
+ let client = new MongoClient(opt.url)
627
+
628
+ //res
629
+ let res = null
630
+ try {
631
+
632
+ //connect, 令連線失敗於寫入前即拋出, 屬影響全部數據之整批性錯誤
633
+ await client.connect()
634
+
635
+ //database, collection
636
+ let database = client.db(opt.db)
637
+ let collection = database.collection(opt.cl)
638
+
639
+ //check
640
+ if (!isarr(data)) {
641
+ data = [data]
642
+ }
643
+
644
+ //check id
645
+ data = map(data, function(v) {
646
+ if (!isestr(v.id)) {
647
+ v.id = genID()
648
+ }
649
+ return v
650
+ })
651
+
652
+ //ensureIndex
653
+ await ensureIndex(collection)
654
+
655
+ //pmSeries
656
+ res = await pmSeries(data, async(v) => {
657
+ return saveOne(collection, v, autoInsert)
658
+ })
659
+
660
+ }
661
+ catch (err) {
662
+ isErr = true
663
+ res = err
664
+ }
665
+ finally {
666
+ await client.close()
667
+ client = null
668
+ }
669
+
670
+ //emit
671
+ if (!isErr) {
672
+ emitChange('save', data, res)
673
+ }
674
+
675
+ if (isErr) {
676
+ return Promise.reject(res)
677
+ }
678
+ return res
679
+ }
680
+
681
+
682
+ /**
683
+ * 刪除數據
684
+ * 註: n為id命中筆數;未帶有效id者視為本筆無法處理,不送查詢條件且以ok為0並附err回報;
685
+ * 判斷本筆是否真的被刪除一律以nDeleted為準
686
+ *
687
+ * @memberOf WOrmMongodb
688
+ * @param {Object|Array} data 輸入數據物件或陣列
689
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列,reject回傳錯誤訊息
690
+ */
691
+ async function del(data) {
692
+ let isErr = false
693
+
694
+ //check
695
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
696
+ return []
697
+ }
698
+
699
+ //cloneDeep
700
+ data = cloneDeep(data)
701
+
702
+ //client
703
+ let client = new MongoClient(opt.url)
704
+
705
+ //res
706
+ let res = null
707
+ try {
708
+
709
+ //connect, 令連線失敗於刪除前即拋出, 屬影響全部數據之整批性錯誤
710
+ await client.connect()
711
+
712
+ //database, collection
713
+ let database = client.db(opt.db)
714
+ let collection = database.collection(opt.cl)
715
+
716
+ //check
717
+ if (!isarr(data)) {
718
+ data = [data]
719
+ }
720
+
721
+ //pmSeries
722
+ res = await pmSeries(data, async(v) => {
723
+
724
+ //id, del不補值, 未帶有效id者視為本筆無法處理
725
+ let id = get(v, 'id')
726
+
727
+ //check, 不得將無效id送進查詢條件, 因undefined經序列化為null會誤中id為null之數據
728
+ if (!isestr(id)) {
729
+ return {
730
+ n: 0,
731
+ nDeleted: 0,
732
+ ok: 0,
733
+ err: `invalid id[${id}]`,
734
+ }
735
+ }
736
+
737
+ //rest
738
+ let rest = null
739
+
740
+ try {
741
+
742
+ //deleteOne
743
+ let r = await collection.deleteOne({ id })
744
+
745
+ //nDeleted
746
+ let nDeleted = get(r, 'deletedCount', 0)
747
+
748
+ //rest, 未命中時nDeleted為0, 屬正常結果
749
+ rest = {
750
+ n: nDeleted,
751
+ nDeleted,
752
+ ok: 1,
753
+ }
754
+
755
+ }
756
+ catch (err) {
757
+
758
+ //本筆失敗不中斷整批
759
+
760
+ //rest
761
+ rest = {
762
+ n: 1,
763
+ nDeleted: 0,
764
+ ok: 0,
765
+ err: genErrMsg(err),
766
+ }
767
+
768
+ }
769
+
770
+ return rest
771
+ })
772
+
773
+ }
774
+ catch (err) {
775
+ isErr = true
776
+ res = err
777
+ }
778
+ finally {
779
+ await client.close()
780
+ client = null
781
+ }
782
+
783
+ //emit
784
+ if (!isErr) {
785
+ emitChange('del', data, res)
786
+ }
787
+
788
+ if (isErr) {
789
+ return Promise.reject(res)
790
+ }
791
+ return res
792
+ }
793
+
794
+
795
+ /**
796
+ * 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
797
+ * 註: n為實際刪除筆數,恆等於nDeleted;條件無命中時回傳0亦屬正常結果
798
+ *
799
+ * @memberOf WOrmMongodb
800
+ * @param {Object} [find={}] 輸入刪除條件物件
801
+ * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
802
+ */
803
+ async function delAll(find = {}) {
804
+ let isErr = false
805
+
806
+ //client
807
+ let client = new MongoClient(opt.url)
808
+
809
+ //res
810
+ let res = null
811
+ try {
812
+
813
+ //database, collection
814
+ let database = client.db(opt.db)
815
+ let collection = database.collection(opt.cl)
816
+
817
+ //deleteMany
818
+ let r = await collection.deleteMany(find)
819
+
820
+ //nDeleted
821
+ let nDeleted = get(r, 'deletedCount', 0)
822
+
823
+ //res
824
+ res = {
825
+ n: nDeleted,
826
+ nDeleted,
827
+ ok: 1,
828
+ }
829
+
830
+ }
831
+ catch (err) {
832
+ isErr = true
833
+ res = err
834
+ }
835
+ finally {
836
+ await client.close()
837
+ client = null
838
+ }
839
+
840
+ //emit
841
+ if (!isErr) {
842
+ emitChange('delAll', null, res)
843
+ }
844
+
845
+ if (isErr) {
846
+ return Promise.reject(res)
847
+ }
848
+ return res
849
+ }
850
+
851
+
852
+ /**
853
+ * 使用GridFS,插入數據,僅於id不存在時寫入,已存在者跳過且不覆寫
854
+ * 數據物件形狀為{ id, u8a },id未給時自動產生,u8a須為Uint8Array
855
+ * 註: n為輸入筆數,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
856
+ *
857
+ * @memberOf WOrmMongodb
858
+ * @param {Object|Array} data 輸入數據物件或陣列,各數據物件形狀為{ id, u8a }
859
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
860
+ */
861
+ async function insertGfs(data) {
862
+ let isErr = false
863
+
864
+ //check
865
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
866
+ return {
867
+ n: 0,
868
+ nInserted: 0,
869
+ ok: 1,
870
+ }
871
+ }
872
+
873
+ //client
874
+ let client = new MongoClient(opt.url)
875
+
876
+ //res
877
+ let res = null
878
+ try {
879
+
880
+ //connect, 令連線失敗於寫入前即拋出, 屬影響全部數據之整批性錯誤
881
+ await client.connect()
882
+
883
+ //database
884
+ let database = client.db(opt.db)
885
+
886
+ //bucket
887
+ let bucket = new mongodb.GridFSBucket(database, {
888
+ chunkSizeBytes: 10 * 1024 * 1024, //10mb
889
+ bucketName: opt.cl
890
+ })
891
+
892
+ //clChunks
893
+ let clChunks = database.collection(`${opt.cl}.chunks`)
894
+
895
+ //check
896
+ if (!isarr(data)) {
897
+ data = [data]
898
+ }
899
+
900
+ //check id與u8a, 僅淺拷貝數據物件而不深拷貝u8a,
901
+ //因u8a僅供讀取, 深拷貝大量二進位數據成本過高;
902
+ //u8a無效屬呼叫端給值錯誤且整批函數無從逐筆回報, 故往外拋
903
+ data = map(data, function(v, k) {
904
+ v = { ...v }
905
+ if (!isestr(v.id)) {
906
+ v.id = genID()
907
+ }
908
+ if (!isu8arr(v.u8a)) {
909
+ throw new Error(`invalid data[${k}].u8a`)
910
+ }
911
+ return v
912
+ })
913
+
914
+ //ensureIndexGfs
915
+ await ensureIndexGfs(database)
916
+
917
+ //nAll, n之基準為輸入筆數
918
+ let nAll = size(data)
919
+
920
+ //上傳, 逐筆序列處理以免大量二進位數據同時佔用記憶體
921
+ let nInserted = 0
922
+ await pmSeries(data, async(v) => {
923
+ let inserted = await _uploadGfs(bucket, clChunks, v.id, v.u8a)
924
+ if (inserted) {
925
+ nInserted++
926
+ }
927
+ })
928
+
929
+ //res, 全數已存在而nInserted為0屬正常結果, 不視為錯誤
930
+ res = {
931
+ n: nAll,
932
+ nInserted,
933
+ ok: 1,
934
+ }
935
+
936
+ }
937
+ catch (err) {
938
+ isErr = true
939
+ res = err
940
+ }
941
+ finally {
942
+ await client.close()
943
+ client = null
944
+ }
945
+
946
+ //emit
947
+ if (!isErr) {
948
+ emitChange('insertGfs', data, res)
949
+ }
950
+
951
+ if (isErr) {
952
+ return Promise.reject(res)
953
+ }
954
+ return res
955
+ }
956
+
957
+
958
+ /**
959
+ * 使用GridFS,由id查詢單筆數據
960
+ * 註: 查無數據或id無效時回傳null,判定基準與selectById一致
961
+ * 本函數不得有副作用,故不建立唯一索引
962
+ *
963
+ * @memberOf WOrmMongodb
964
+ * @param {String} id 輸入查詢id字串
965
+ * @returns {Promise} 回傳Promise,resolve回傳數據物件{ id, u8a },若無此id或id無效則回傳null,reject回傳錯誤訊息
966
+ */
967
+ async function selectByIdGfs(id) {
968
+ let isErr = false
969
+
970
+ //check
971
+ if (!isestr(id)) {
972
+ //未給有效id視為查無數據, 判定基準與selectById一致
973
+ return null
974
+ }
975
+
976
+ //client
977
+ let client = new MongoClient(opt.url)
978
+
979
+ //core
980
+ let core = async (id) => {
981
+
982
+ //pm
983
+ let pm = genPm()
984
+
985
+ //database
986
+ let database = client.db(opt.db)
987
+
988
+ //bucket
989
+ let bucket = new mongodb.GridFSBucket(database, {
990
+ chunkSizeBytes: 10 * 1024 * 1024, //10mb
991
+ bucketName: opt.cl
992
+ })
993
+
994
+ //buf
995
+ let buf = Buffer.from('')
996
+
997
+ //stream
998
+ let sm = bucket.openDownloadStreamByName(id)
999
+ sm.on('data', function (chunk) {
1000
+ buf = Buffer.concat([buf, chunk])
1001
+ })
1002
+ sm.on('error', function (err) {
1003
+
1004
+ //reject
1005
+ pm.reject(err)
1006
+
1007
+ })
1008
+ sm.on('end', function () {
1009
+
1010
+ //u8a
1011
+ let u8a = new Uint8Array(buf)
1012
+
1013
+ //clean memory
1014
+ buf = null
1015
+
1016
+ //resolve
1017
+ pm.resolve(u8a)
1018
+
1019
+ })
1020
+
1021
+ return pm
1022
+ }
1023
+
1024
+ //res
1025
+ let res = null
1026
+ try {
1027
+
1028
+ //core
1029
+ let u8a = await core(id)
1030
+
1031
+ //res, 形狀與insertGfs所收之數據物件一致
1032
+ res = {
1033
+ id,
1034
+ u8a,
1035
+ }
1036
+
1037
+ }
1038
+ catch (err) {
1039
+
1040
+ //check, GridFS查無檔案時驅動以code為ENOENT之錯誤回報, 屬正常結果故回傳null
1041
+ if (get(err, 'code') === 'ENOENT') {
1042
+ res = null
1043
+ }
1044
+ else {
1045
+ isErr = true
1046
+ res = err
1047
+ }
1048
+
1049
+ }
1050
+ finally {
1051
+ await client.close()
1052
+ client = null
1053
+ }
1054
+
1055
+ if (isErr) {
1056
+ return Promise.reject(res)
1057
+ }
1058
+ return res
1059
+ }
1060
+
1061
+
1062
+ async function _findGfs(find = {}, bucket) {
1063
+ let isErr = false
1064
+
1065
+ //res
1066
+ let res = null
1067
+ try {
1068
+
1069
+ //find
1070
+ let cursor = bucket.find(find)
1071
+
1072
+ //toArray
1073
+ res = await cursor.toArray()
1074
+
1075
+ }
1076
+ catch (err) {
1077
+ isErr = true
1078
+ res = err
1079
+ }
1080
+
1081
+ if (isErr) {
1082
+ return Promise.reject(res)
1083
+ }
1084
+ return res
1085
+ }
1086
+
1087
+
1088
+ /**
1089
+ * 使用GridFS,刪除數據
1090
+ * 註: n為id命中筆數;未帶有效id者視為本筆無法處理,以ok為0並附err回報;
1091
+ * 判斷本筆是否真的被刪除一律以nDeleted為準
1092
+ *
1093
+ * @memberOf WOrmMongodb
1094
+ * @param {Object|Array} data 輸入數據物件或陣列,各數據物件須帶id
1095
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列,reject回傳錯誤訊息
1096
+ */
1097
+ async function delGfs(data) {
1098
+ let isErr = false
1099
+
1100
+ //check
1101
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
1102
+ return []
1103
+ }
1104
+
1105
+ //client
1106
+ let client = new MongoClient(opt.url)
1107
+
1108
+ //res
1109
+ let res = null
1110
+ try {
1111
+
1112
+ //connect, 令連線失敗於刪除前即拋出, 屬影響全部數據之整批性錯誤
1113
+ await client.connect()
1114
+
1115
+ //database
1116
+ let database = client.db(opt.db)
1117
+
1118
+ //bucket
1119
+ let bucket = new mongodb.GridFSBucket(database, {
1120
+ chunkSizeBytes: 10 * 1024 * 1024, //10mb
1121
+ bucketName: opt.cl
1122
+ })
1123
+
1124
+ //check
1125
+ if (!isarr(data)) {
1126
+ data = [data]
1127
+ }
1128
+
1129
+ //pmSeries
1130
+ res = await pmSeries(data, async(v) => {
1131
+
1132
+ //id, delGfs不補值, 未帶有效id者視為本筆無法處理
1133
+ let id = get(v, 'id')
1134
+
1135
+ //check, 判定基準與del一致
1136
+ if (!isestr(id)) {
1137
+ return {
1138
+ n: 0,
1139
+ nDeleted: 0,
1140
+ ok: 0,
1141
+ err: `invalid id[${id}]`,
1142
+ }
1143
+ }
1144
+
1145
+ //rest
1146
+ let rest = null
1147
+
1148
+ try {
1149
+
1150
+ //_findGfs
1151
+ let ltdt = await _findGfs({ filename: id }, bucket)
1152
+
1153
+ //delete, 建立唯一索引後同一id至多一筆,
1154
+ //既有數據若尚存重複id則一併刪除並如實回報nDeleted
1155
+ let nDeleted = 0
1156
+ for (let vv of ltdt) {
1157
+ await bucket.delete(vv._id)
1158
+ nDeleted++
1159
+ }
1160
+
1161
+ //rest, n為命中與否, 未命中時兩者皆為0且屬正常結果
1162
+ rest = {
1163
+ n: nDeleted > 0 ? 1 : 0,
1164
+ nDeleted,
1165
+ ok: 1,
1166
+ }
1167
+
1168
+ }
1169
+ catch (err) {
1170
+
1171
+ //本筆失敗不中斷整批
1172
+
1173
+ //rest
1174
+ rest = {
1175
+ n: 1,
1176
+ nDeleted: 0,
1177
+ ok: 0,
1178
+ err: genErrMsg(err),
1179
+ }
1180
+
1181
+ }
1182
+
1183
+ return rest
1184
+ })
1185
+
1186
+ }
1187
+ catch (err) {
1188
+ isErr = true
1189
+ res = err
1190
+ }
1191
+ finally {
1192
+ await client.close()
1193
+ client = null
1194
+ }
1195
+
1196
+ //emit
1197
+ if (!isErr) {
1198
+ emitChange('delGfs', data, res)
1199
+ }
1200
+
1201
+ if (isErr) {
1202
+ return Promise.reject(res)
1203
+ }
1204
+ return res
1205
+ }
1206
+
1207
+
1208
+ async function _delGfs(bid, bucket) {
1209
+
1210
+ //delete, 連同chunks一併刪除
1211
+ await bucket.delete(bid)
1212
+
1213
+ }
1214
+
1215
+
1216
+ /**
1217
+ * 使用GridFS,刪除全部數據,需與delGfs分開,避免未傳數據導致直接刪除全部
1218
+ * 註: n為實際刪除筆數,恆等於nDeleted;條件無命中時回傳0亦屬正常結果
1219
+ * 本函數不建立唯一索引,以免既有數據尚存重複id時無法清除
1220
+ *
1221
+ * @memberOf WOrmMongodb
1222
+ * @param {Object} [find={}] 輸入刪除條件物件
1223
+ * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
1224
+ */
1225
+ async function delAllGfs(find = {}) {
1226
+ let isErr = false
1227
+
1228
+ //client
1229
+ let client = new MongoClient(opt.url)
1230
+
1231
+ //res
1232
+ let res = null
1233
+ try {
1234
+
1235
+ //database
1236
+ let database = client.db(opt.db)
1237
+
1238
+ //bucket
1239
+ let bucket = new mongodb.GridFSBucket(database, {
1240
+ chunkSizeBytes: 10 * 1024 * 1024, //10mb
1241
+ bucketName: opt.cl
1242
+ })
1243
+
1244
+ //_findGfs
1245
+ let ltdt = await _findGfs(find, bucket)
1246
+
1247
+ //ps
1248
+ let ps = map(ltdt, function(v) {
1249
+ let bid = v._id
1250
+ return _delGfs(bid, bucket)
1251
+ })
1252
+
1253
+ //all, 任一刪除失敗即往外拋, 屬整批性錯誤
1254
+ await Promise.all(ps)
1255
+
1256
+ //nDeleted, 全數刪除成功方會執行至此, 故實際刪除筆數即為符合條件筆數
1257
+ let nDeleted = size(ltdt)
1258
+
1259
+ //res
1260
+ res = {
1261
+ n: nDeleted,
1262
+ nDeleted,
1263
+ ok: 1,
1264
+ }
1265
+
1266
+ }
1267
+ catch (err) {
1268
+ isErr = true
1269
+ res = err
1270
+ }
1271
+ finally {
1272
+ await client.close()
1273
+ client = null
1274
+ }
1275
+
1276
+ //emit
1277
+ if (!isErr) {
1278
+ emitChange('delAllGfs', null, res)
1279
+ }
1280
+
1281
+ if (isErr) {
1282
+ return Promise.reject(res)
1283
+ }
1284
+ return res
1285
+ }
1286
+
1287
+
1288
+ //bind
1289
+ ee.select = select
1290
+ ee.selectById = selectById
1291
+ ee.insert = insert
1292
+ ee.save = save
1293
+ ee.del = del
1294
+ ee.delAll = delAll
1295
+ ee.selectByIdGfs = selectByIdGfs
1296
+ ee.insertGfs = insertGfs
1297
+ ee.delGfs = delGfs
1298
+ ee.delAllGfs = delAllGfs
1299
+
1300
+
1301
+ return ee
1302
+ }
1303
+
1304
+
1305
+ export default WOrmMongodb
875
1306
  </code></pre>
876
1307
  </article>
877
1308
  </section>
@@ -886,7 +1317,7 @@ export default WOrmMongodb
886
1317
  <br class="clear">
887
1318
 
888
1319
  <footer>
889
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 08 2026 14:31:31 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1320
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 16 2026 22:37:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
890
1321
  </footer>
891
1322
 
892
1323
  <script>prettyPrint();</script>