w-orm-mongodb 1.1.42 → 1.1.44

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#.selectByPk">selectByPk</a></li><li data-type='method'><a href="WOrmMongodb.html#.selectByPkGfs">selectByPkGfs</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#.insertBulk">insertBulk</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
 
@@ -48,7 +48,9 @@
48
48
  <pre class="prettyprint source linenums"><code>import mongodb from 'mongodb'
49
49
  import stream from 'stream'
50
50
  import cloneDeep from 'lodash-es/cloneDeep.js'
51
+ import each from 'lodash-es/each.js'
51
52
  import every from 'lodash-es/every.js'
53
+ import filter from 'lodash-es/filter.js'
52
54
  import get from 'lodash-es/get.js'
53
55
  import map from 'lodash-es/map.js'
54
56
  import omit from 'lodash-es/omit.js'
@@ -61,6 +63,7 @@ import isestr from 'wsemi/src/isestr.mjs'
61
63
  import isarr from 'wsemi/src/isarr.mjs'
62
64
  import isearr from 'wsemi/src/isearr.mjs'
63
65
  import iseobj from 'wsemi/src/iseobj.mjs'
66
+ import isnum from 'wsemi/src/isnum.mjs'
64
67
  import isu8arr from 'wsemi/src/isu8arr.mjs'
65
68
  import pmSeries from 'wsemi/src/pmSeries.mjs'
66
69
 
@@ -110,6 +113,10 @@ function WOrmMongodb(opt = {}) {
110
113
  let _indexGfsReady = false
111
114
 
112
115
 
116
+ //_canUseTx, 拓樸判定只須一次, 以旗標記錄避免每次操作皆多一次round-trip
117
+ let _canUseTx = null
118
+
119
+
113
120
  //ee, 採wsemi之evem(即eventemitter3), 其於'error'無監聽者時僅回傳false而不拋出,
114
121
  //故本套件之操作行為不因呼叫端有無註冊監聽而改變; Node內建之EventEmitter具該拋出語義, 不可用
115
122
  let ee = evem()
@@ -458,18 +465,31 @@ function WOrmMongodb(opt = {}) {
458
465
  /**
459
466
  * 插入數據,僅於id不存在時寫入,已存在者跳過且不覆寫
460
467
  * 由MongoDB於唯一索引上原子完成[檢查id未存在]與[寫入],併發時同一id僅有一次成功
461
- * 註: n為輸入筆數,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
468
+ * 註: 預設回傳單一聚合物件,n為輸入筆數,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
469
+ * 註: option.returnList為true時改回與輸入等長且保序之逐筆陣列,供呼叫端得知[是哪幾筆]為新數據,
470
+ * 聚合計數僅能回答[有幾筆],而去重類呼叫端須知何者為新方能對其執行下游動作
462
471
  * 註: opt.autoGenPk為true(預設)時未帶有效id者自動產生,為false時未帶有效id即reject且同批皆不寫入
463
472
  *
464
473
  * @memberOf WOrmMongodb
465
474
  * @param {Object|Array} data 輸入數據物件或陣列
466
- * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
475
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
476
+ * @param {Boolean} [option.returnList=false] 輸入是否改回逐筆結果陣列,預設為false。為true時回傳與輸入等長且保序之陣列,各元素為{ n, nInserted, ok },已插入者nInserted為1、已存在而跳過者為0
477
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果,returnList為true時回傳逐筆結果陣列,reject回傳錯誤訊息
467
478
  */
468
- async function insert(data) {
479
+ async function insert(data, option = {}) {
469
480
  let isErr = false
470
481
 
482
+ //returnList
483
+ let returnList = get(option, 'returnList')
484
+ if (!isbol(returnList)) {
485
+ returnList = false
486
+ }
487
+
471
488
  //check
472
489
  if (!iseobj(data) &amp;&amp; !isearr(data)) {
490
+ if (returnList) {
491
+ return []
492
+ }
473
493
  return {
474
494
  n: 0,
475
495
  nInserted: 0,
@@ -510,7 +530,10 @@ function WOrmMongodb(opt = {}) {
510
530
 
511
531
  //insertMany, ordered:false令已存在id者跳過而不中斷整批插入,
512
532
  //同批含重複id時亦僅首筆成功, 故不須逐筆插入即可取得實際插入筆數
533
+ //ordered:false時writeErrors之index即為未插入者於輸入內之位置, 其餘即為已插入者,
534
+ //故逐筆判定亦由本次同一往返取得, 不須額外查詢
513
535
  let nInserted = 0
536
+ let kpSkip = {}
514
537
  try {
515
538
  let r = await collection.insertMany(data, { ordered: false })
516
539
  nInserted = r.insertedCount
@@ -525,12 +548,240 @@ function WOrmMongodb(opt = {}) {
525
548
  //重複鍵錯誤時仍可由result取得實際插入筆數
526
549
  nInserted = get(err, 'result.insertedCount', 0)
527
550
 
551
+ //kpSkip, 記錄未插入者於輸入內之位置
552
+ each(get(err, 'writeErrors', []), function(v) {
553
+ let k = get(v, 'index', get(v, 'err.index'))
554
+ if (isnum(k)) {
555
+ kpSkip[k] = true
556
+ }
557
+ })
558
+
528
559
  }
529
560
 
530
- //res, 全數已存在而nInserted為0屬正常結果, 不視為錯誤
561
+ //res
562
+ if (returnList) {
563
+
564
+ //res, 與輸入等長且保序, 各元素之n恆為1、ok恆為1,
565
+ //因insert之任何錯誤皆屬整批性錯誤而reject, 逐筆元素不出現ok為0與err
566
+ res = map(data, function(v, k) {
567
+ return {
568
+ n: 1,
569
+ nInserted: kpSkip[k] === true ? 0 : 1,
570
+ ok: 1,
571
+ }
572
+ })
573
+
574
+ }
575
+ else {
576
+
577
+ //res, 全數已存在而nInserted為0屬正常結果, 不視為錯誤
578
+ res = {
579
+ n: nAll,
580
+ nInserted,
581
+ ok: 1,
582
+ }
583
+
584
+ }
585
+
586
+ }
587
+ catch (err) {
588
+ isErr = true
589
+ res = err
590
+ }
591
+ finally {
592
+ await client.close()
593
+ client = null
594
+ }
595
+
596
+ //emit
597
+ if (!isErr) {
598
+ emitChange('insert', data, res)
599
+ }
600
+
601
+ //check
602
+ if (isErr) {
603
+
604
+ //emit, 整批性錯誤須於reject之前發出
605
+ emitError('insert', data, res)
606
+
607
+ return Promise.reject(res)
608
+ }
609
+ return res
610
+ }
611
+
612
+
613
+ /**
614
+ * 判定本次部署是否支援交易
615
+ * 註: MongoDB之交易須replica set或分片叢集,standalone不支援
616
+ * 判定依據為hello回應:replica set帶setName,連至mongos者msg為isdbgrid,standalone兩者皆無
617
+ *
618
+ * @ignore
619
+ * @param {Object} client 輸入MongoClient物件
620
+ * @returns {Promise} 回傳Promise,resolve回傳是否支援交易布林值,reject回傳錯誤訊息
621
+ */
622
+ async function canUseTx(client) {
623
+
624
+ //check
625
+ if (isbol(_canUseTx)) {
626
+ return _canUseTx
627
+ }
628
+
629
+ //hello
630
+ let hello = await client.db(opt.db).admin().command({ hello: 1 })
631
+
632
+ //_canUseTx
633
+ _canUseTx = isestr(get(hello, 'setName')) || get(hello, 'msg') === 'isdbgrid'
634
+
635
+ return _canUseTx
636
+ }
637
+
638
+
639
+ /**
640
+ * 以交易達成insertBulk之全有全無
641
+ * 交易內任一筆失敗即整批回滾,故不會寫入任何一筆,無須補償動作
642
+ *
643
+ * @ignore
644
+ * @param {Object} client 輸入MongoClient物件
645
+ * @param {Object} collection 輸入資料表物件
646
+ * @param {Array} data 輸入數據陣列
647
+ * @returns {Promise} 回傳Promise,resolve回傳undefined,reject回傳錯誤訊息
648
+ */
649
+ async function _insertBulkByTx(client, collection, data) {
650
+
651
+ //session
652
+ let session = client.startSession()
653
+
654
+ try {
655
+
656
+ //withTransaction, ordered:true令首筆衝突即停止, 由交易回滾保證未寫入任何一筆
657
+ await session.withTransaction(async () => {
658
+ await collection.insertMany(data, { session, ordered: true })
659
+ })
660
+
661
+ }
662
+ finally {
663
+ await session.endSession()
664
+ }
665
+
666
+ }
667
+
668
+
669
+ /**
670
+ * 以補償動作達成insertBulk之全有全無,供無交易可用之standalone使用
671
+ * 註: 補償係刪除本次呼叫已寫入者,其_id由驅動於送出前在用戶端產生,
672
+ * 與呼叫前既有數據之_id必不相同,故縱使該筆之id已存在亦不會誤刪既有數據;
673
+ * 刪除未寫入者為無操作,故不須精確得知哪幾筆已寫入
674
+ * 註: 行程若於補償途中中止(斷電、強制終止),已寫入之部份可能殘留,此為無交易可用之固有限制
675
+ *
676
+ * @ignore
677
+ * @param {Object} collection 輸入資料表物件
678
+ * @param {Array} data 輸入數據陣列
679
+ * @returns {Promise} 回傳Promise,resolve回傳undefined,reject回傳錯誤訊息
680
+ */
681
+ async function _insertBulkByCompensate(collection, data) {
682
+
683
+ //errIns
684
+ let errIns = null
685
+
686
+ try {
687
+ await collection.insertMany(data, { ordered: false })
688
+ }
689
+ catch (err) {
690
+ errIns = err
691
+ }
692
+
693
+ //check, 全數插入成功即無須補償
694
+ if (errIns === null) {
695
+ return
696
+ }
697
+
698
+ //idDels, 取本次由驅動所產生之全部_id
699
+ let idDels = map(data, function(v) {
700
+ return v._id
701
+ })
702
+ idDels = filter(idDels, function(v) {
703
+ return v !== undefined &amp;&amp; v !== null
704
+ })
705
+
706
+ //補償, 刪除本次已寫入者
707
+ if (size(idDels) > 0) {
708
+ await collection.deleteMany({ _id: { $in: idDels } })
709
+ }
710
+
711
+ throw errIns
712
+ }
713
+
714
+
715
+ /**
716
+ * 批次插入數據,全批視為一個單位:全部插入成功,或一筆都不寫入
717
+ * 任一筆之id已存在(含同批含重複id)即以reject拋出整批性錯誤,且不寫入任何一筆
718
+ * 註: 本函數非insert之加速版,兩者衝突政策不同——insert跳過已存在者而整批ok為1,
719
+ * 本函數則整批reject;需要逐筆處置者改用insert
720
+ * 註: n為輸入筆數,nInserted於成功時恆等於n;不提供逐筆結果,故不出現ok為0與err
721
+ * 註: opt.autoGenPk為true(預設)時未帶有效id者自動產生,為false時未帶有效id即reject且同批皆不寫入
722
+ * 註: 全有全無之達成方式依部署而異——具replica set或分片叢集者以交易包覆,
723
+ * standalone無交易可用而以補償動作達成,其限制詳見_insertBulkByCompensate
724
+ *
725
+ * @memberOf WOrmMongodb
726
+ * @param {Object|Array} data 輸入數據物件或陣列
727
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
728
+ */
729
+ async function insertBulk(data) {
730
+ let isErr = false
731
+
732
+ //check
733
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
734
+ return {
735
+ n: 0,
736
+ nInserted: 0,
737
+ ok: 1,
738
+ }
739
+ }
740
+
741
+ //cloneDeep
742
+ data = cloneDeep(data)
743
+
744
+ //client
745
+ let client = new MongoClient(opt.url)
746
+
747
+ //res
748
+ let res = null
749
+ try {
750
+
751
+ //connect, 令連線失敗於寫入前即拋出, 屬影響全部數據之整批性錯誤
752
+ await client.connect()
753
+
754
+ //database, collection
755
+ let database = client.db(opt.db)
756
+ let collection = database.collection(opt.cl)
757
+
758
+ //check
759
+ if (!isarr(data)) {
760
+ data = [data]
761
+ }
762
+
763
+ //check id, 須於任何寫入之前一次完成
764
+ data = map(data, procPk)
765
+
766
+ //ensureIndex
767
+ await ensureIndex(collection)
768
+
769
+ //nAll, n之基準為輸入筆數
770
+ let nAll = size(data)
771
+
772
+ //check, 有交易可用者一律優先採用交易, 其回滾即為全有全無之保證
773
+ let bTx = await canUseTx(client)
774
+ if (bTx) {
775
+ await _insertBulkByTx(client, collection, data)
776
+ }
777
+ else {
778
+ await _insertBulkByCompensate(collection, data)
779
+ }
780
+
781
+ //res, 執行至此表全數插入成功, 故nInserted恆等於n
531
782
  res = {
532
783
  n: nAll,
533
- nInserted,
784
+ nInserted: nAll,
534
785
  ok: 1,
535
786
  }
536
787
 
@@ -546,14 +797,14 @@ function WOrmMongodb(opt = {}) {
546
797
 
547
798
  //emit
548
799
  if (!isErr) {
549
- emitChange('insert', data, res)
800
+ emitChange('insertBulk', data, res)
550
801
  }
551
802
 
552
803
  //check
553
804
  if (isErr) {
554
805
 
555
806
  //emit, 整批性錯誤須於reject之前發出
556
- emitError('insert', data, res)
807
+ emitError('insertBulk', data, res)
557
808
 
558
809
  return Promise.reject(res)
559
810
  }
@@ -1427,6 +1678,7 @@ function WOrmMongodb(opt = {}) {
1427
1678
  ee.select = select
1428
1679
  ee.selectByPk = selectByPk
1429
1680
  ee.insert = insert
1681
+ ee.insertBulk = insertBulk
1430
1682
  ee.save = save
1431
1683
  ee.del = del
1432
1684
  ee.delAll = delAll
@@ -1455,7 +1707,7 @@ export default WOrmMongodb
1455
1707
  <br class="clear">
1456
1708
 
1457
1709
  <footer>
1458
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 09:16:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1710
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 18:43:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1459
1711
  </footer>
1460
1712
 
1461
1713
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -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#.selectByPk">selectByPk</a></li><li data-type='method'><a href="WOrmMongodb.html#.selectByPkGfs">selectByPkGfs</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#.insertBulk">insertBulk</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
 
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 09:16:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 18:43:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
package/g-basic.mjs CHANGED
@@ -46,11 +46,17 @@ async function test() {
46
46
  //wo
47
47
  let wo = WOrm(opt)
48
48
 
49
- //on
49
+ //on change, 資料實際異動成功後發出
50
50
  wo.on('change', function(mode, data, res) {
51
51
  console.log('change', mode)
52
52
  })
53
53
 
54
+ //on error, 操作發生錯誤時發出, 整批性錯誤於reject前、逐筆失敗於該筆定案後
55
+ //註: 事件僅為附加通知, 錯誤仍可由reject或逐筆之err欄位取得; 正常結果不會發出
56
+ wo.on('error', function(mode, data, err) {
57
+ console.log('error', mode, err)
58
+ })
59
+
54
60
  //delAll
55
61
  await wo.delAll()
56
62
  .then(function(msg) {
package/g-gfs.mjs CHANGED
@@ -24,11 +24,17 @@ async function test() {
24
24
  //wo
25
25
  let wo = WOrm(opt)
26
26
 
27
- //on
27
+ //on change, 資料實際異動成功後發出
28
28
  wo.on('change', function(mode, data, res) {
29
29
  console.log('change', mode)
30
30
  })
31
31
 
32
+ //on error, 操作發生錯誤時發出, 整批性錯誤於reject前、逐筆失敗於該筆定案後
33
+ //註: 事件僅為附加通知, 錯誤仍可由reject或逐筆之err欄位取得; 正常結果不會發出
34
+ wo.on('error', function(mode, data, err) {
35
+ console.log('error', mode, err)
36
+ })
37
+
32
38
  //u8a, 亦可為瀏覽器或nodejs取得之任何Uint8Array
33
39
  let u8a = genU8a(1000)
34
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "w-orm-mongodb",
3
- "version": "1.1.42",
3
+ "version": "1.1.44",
4
4
  "main": "dist/w-orm-mongodb.umd.js",
5
5
  "dependencies": {
6
6
  "mongodb": "^7.5.0",