w-orm-mongodb 1.1.37 → 1.1.39

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