w-orm-mongodb 1.1.39 → 1.1.41

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.
@@ -1,4 +1,3 @@
1
- import events from 'events'
2
1
  import mongodb from 'mongodb'
3
2
  import stream from 'stream'
4
3
  import cloneDeep from 'lodash-es/cloneDeep.js'
@@ -7,8 +6,10 @@ import get from 'lodash-es/get.js'
7
6
  import map from 'lodash-es/map.js'
8
7
  import omit from 'lodash-es/omit.js'
9
8
  import size from 'lodash-es/size.js'
9
+ import evem from 'wsemi/src/evem.mjs'
10
10
  import genPm from 'wsemi/src/genPm.mjs'
11
11
  import genID from 'wsemi/src/genID.mjs'
12
+ import isbol from 'wsemi/src/isbol.mjs'
12
13
  import isestr from 'wsemi/src/isestr.mjs'
13
14
  import isarr from 'wsemi/src/isarr.mjs'
14
15
  import isearr from 'wsemi/src/isearr.mjs'
@@ -20,14 +21,16 @@ import pmSeries from 'wsemi/src/pmSeries.mjs'
20
21
  /**
21
22
  * 操作資料庫(MongoDB)
22
23
  *
23
- * 本套件之主鍵欄位固定為id,尚未支援由呼叫端指定。
24
- * id為無業務語義之識別碼,故insert與save於輸入未帶有效id時自動補值;del不補值。
24
+ * 本套件之主鍵欄位固定為id,尚未支援由呼叫端指定。id為無業務語義之識別碼。
25
+ * opt.autoGenPk預設為true,insert、saveinsertGfs於輸入未帶有效id時自動產生;del於任一設定下皆不補值。
26
+ * opt.autoGenPk為false時套件一律不產生id,未帶有效id者以reject拋出,且id之唯一性與格式皆由呼叫端自負。
25
27
  *
26
28
  * @class
27
29
  * @param {Object} [opt={}] 輸入設定物件,預設{}
28
30
  * @param {String} [opt.url='mongodb://127.0.0.1:27017'] 輸入連接資料庫字串,預設'mongodb://127.0.0.1:27017'
29
31
  * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
30
32
  * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
33
+ * @param {Boolean} [opt.autoGenPk=true] 輸入是否於輸入未帶有效主鍵時自動產生主鍵值,預設true。為false時主鍵須由呼叫端自備,屬依賴注入之定位,主鍵之唯一性、格式與是否與既有資料衝突皆由呼叫端自負。本設定為建構層設定,不得於insert與save之option逐次覆寫
31
34
  * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
32
35
  */
33
36
  function WOrmMongodb(opt = {}) {
@@ -45,6 +48,13 @@ function WOrmMongodb(opt = {}) {
45
48
  }
46
49
 
47
50
 
51
+ //autoGenPk, 預設開啟, 為false時主鍵一律由呼叫端自備, 套件不產生亦不補救
52
+ let autoGenPk = get(opt, 'autoGenPk')
53
+ if (!isbol(autoGenPk)) {
54
+ autoGenPk = true
55
+ }
56
+
57
+
48
58
  //_indexReady, 唯一索引只須建立一次, 以旗標記錄避免每次操作皆多一次round-trip
49
59
  let _indexReady = false
50
60
 
@@ -53,8 +63,9 @@ function WOrmMongodb(opt = {}) {
53
63
  let _indexGfsReady = false
54
64
 
55
65
 
56
- //ee
57
- let ee = new events.EventEmitter()
66
+ //ee, 採wsemi之evem(即eventemitter3), 其於'error'無監聽者時僅回傳false而不拋出,
67
+ //故本套件之操作行為不因呼叫端有無註冊監聽而改變; Node內建之EventEmitter具該拋出語義, 不可用
68
+ let ee = evem()
58
69
 
59
70
 
60
71
  //MongoClient
@@ -68,7 +79,7 @@ function WOrmMongodb(opt = {}) {
68
79
  * @param {Error|String} err 輸入錯誤物件或字串
69
80
  * @returns {String} 回傳錯誤訊息字串
70
81
  */
71
- function genErrMsg(err) {
82
+ function getErrMsg(err) {
72
83
 
73
84
  //message
74
85
  let message = get(err, 'message')
@@ -99,6 +110,59 @@ function WOrmMongodb(opt = {}) {
99
110
  }
100
111
 
101
112
 
113
+ /**
114
+ * 發出error事件,操作發生錯誤時發出,錯誤訊息一律轉為字串
115
+ * 註: 事件僅為附加通知,所送出之資訊必另有正規管道——整批性錯誤經Promise.reject,逐筆失敗經該筆之err欄位
116
+ * 註: 訂閱函數拋錯不得影響本次操作之結果,故另包try並自行吞掉
117
+ * 註: 正常結果不得發出本事件,如insert全數已存在、save合併後內容相同、del主鍵未命中、selectByPk查無數據
118
+ *
119
+ * @ignore
120
+ * @param {String} mode 輸入操作別字串
121
+ * @param {Array|null} data 輸入本次操作之數據
122
+ * @param {Error|String} err 輸入錯誤物件或字串
123
+ * @returns {undefined} 無回傳值
124
+ */
125
+ function emitError(mode, data, err) {
126
+ try {
127
+ ee.emit('error', mode, data, getErrMsg(err))
128
+ }
129
+ catch (errEmit) {
130
+ console.log(errEmit)
131
+ }
132
+ }
133
+
134
+
135
+ /**
136
+ * 檢查並補齊單筆數據之主鍵
137
+ * autoGenPk為true時未帶有效id者自動產生,為false時往外拋
138
+ * 註: 未帶有效id屬呼叫端未履行契約而非某一筆資料本身之問題,故為整批性錯誤而不降級為該筆ok為0,
139
+ * 若降級為逐筆結果,呼叫端易於整批resolve之下漏看,使[忘了給id]靜默變成[少寫了幾筆]
140
+ * 註: 本函數須於任何寫入之前一次對全部數據完成,令拋錯時同批之有效筆數亦不會被寫入
141
+ *
142
+ * @ignore
143
+ * @param {Object} v 輸入數據物件
144
+ * @param {Number} k 輸入數據於陣列內之索引
145
+ * @returns {Object} 回傳補齊主鍵之數據物件
146
+ */
147
+ function procPk(v, k) {
148
+
149
+ //check
150
+ if (!isestr(v.id)) {
151
+
152
+ //check, autoGenPk為false時主鍵須由呼叫端自備
153
+ if (!autoGenPk) {
154
+ throw new Error(`invalid data[${k}].id, autoGenPk is false`)
155
+ }
156
+
157
+ //genID
158
+ v.id = genID()
159
+
160
+ }
161
+
162
+ return v
163
+ }
164
+
165
+
102
166
  /**
103
167
  * 判定是否為唯一索引重複鍵錯誤(11000),批次插入時須全部寫入錯誤皆為重複鍵才算
104
168
  *
@@ -269,7 +333,12 @@ function WOrmMongodb(opt = {}) {
269
333
  client = null
270
334
  }
271
335
 
336
+ //check
272
337
  if (isErr) {
338
+
339
+ //emit, 整批性錯誤須於reject之前發出
340
+ emitError('select', null, res)
341
+
273
342
  return Promise.reject(res)
274
343
  }
275
344
  return res
@@ -327,7 +396,12 @@ function WOrmMongodb(opt = {}) {
327
396
  client = null
328
397
  }
329
398
 
399
+ //check
330
400
  if (isErr) {
401
+
402
+ //emit, 整批性錯誤須於reject之前發出
403
+ emitError('selectByPk', null, res)
404
+
331
405
  return Promise.reject(res)
332
406
  }
333
407
  return res
@@ -338,6 +412,7 @@ function WOrmMongodb(opt = {}) {
338
412
  * 插入數據,僅於id不存在時寫入,已存在者跳過且不覆寫
339
413
  * 由MongoDB於唯一索引上原子完成[檢查id未存在]與[寫入],併發時同一id僅有一次成功
340
414
  * 註: n為輸入筆數,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
415
+ * 註: opt.autoGenPk為true(預設)時未帶有效id者自動產生,為false時未帶有效id即reject且同批皆不寫入
341
416
  *
342
417
  * @memberOf WOrmMongodb
343
418
  * @param {Object|Array} data 輸入數據物件或陣列
@@ -377,13 +452,8 @@ function WOrmMongodb(opt = {}) {
377
452
  data = [data]
378
453
  }
379
454
 
380
- //check id
381
- data = map(data, function(v) {
382
- if (!isestr(v.id)) {
383
- v.id = genID()
384
- }
385
- return v
386
- })
455
+ //check id, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
456
+ data = map(data, procPk)
387
457
 
388
458
  //ensureIndex
389
459
  await ensureIndex(collection)
@@ -432,7 +502,12 @@ function WOrmMongodb(opt = {}) {
432
502
  emitChange('insert', data, res)
433
503
  }
434
504
 
505
+ //check
435
506
  if (isErr) {
507
+
508
+ //emit, 整批性錯誤須於reject之前發出
509
+ emitError('insert', data, res)
510
+
436
511
  return Promise.reject(res)
437
512
  }
438
513
  return res
@@ -537,7 +612,7 @@ function WOrmMongodb(opt = {}) {
537
612
  nInserted: 0,
538
613
  nModified: 0,
539
614
  ok: 0,
540
- err: genErrMsg(err),
615
+ err: getErrMsg(err),
541
616
  }
542
617
 
543
618
  break
@@ -550,6 +625,11 @@ function WOrmMongodb(opt = {}) {
550
625
  emitChange('insert', [v], rest)
551
626
  }
552
627
 
628
+ //emit, 逐筆失敗須於該筆結果定案後發出, 每筆一次
629
+ if (rest.ok === 0) {
630
+ emitError('save', [v], rest.err)
631
+ }
632
+
553
633
  return rest
554
634
  }
555
635
 
@@ -558,6 +638,7 @@ function WOrmMongodb(opt = {}) {
558
638
  * 儲存數據,以id為準更新既有數據,未給之欄位會保留;id不存在且option.autoInsert為true時改為插入
559
639
  * 註: n為id命中筆數,命中或經插入而產生皆為1;[內容相同]之判定基準為將待儲存物件合併進現值後結果與現值相同,
560
640
  * 相同者不寫入而nModified為0;本筆失敗不中斷整批,該筆以ok為0並附err回報
641
+ * 註: opt.autoGenPk為true(預設)時未帶有效id者自動產生,為false時未帶有效id即reject且同批皆不寫入
561
642
  *
562
643
  * @memberOf WOrmMongodb
563
644
  * @param {Object|Array} data 輸入數據物件或陣列
@@ -598,13 +679,8 @@ function WOrmMongodb(opt = {}) {
598
679
  data = [data]
599
680
  }
600
681
 
601
- //check id
602
- data = map(data, function(v) {
603
- if (!isestr(v.id)) {
604
- v.id = genID()
605
- }
606
- return v
607
- })
682
+ //check id, 須於任何寫入之前一次完成, 令autoGenPk為false而拋錯時同批之有效筆數亦不會被寫入
683
+ data = map(data, procPk)
608
684
 
609
685
  //ensureIndex
610
686
  await ensureIndex(collection)
@@ -629,7 +705,12 @@ function WOrmMongodb(opt = {}) {
629
705
  emitChange('save', data, res)
630
706
  }
631
707
 
708
+ //check
632
709
  if (isErr) {
710
+
711
+ //emit, 整批性錯誤須於reject之前發出
712
+ emitError('save', data, res)
713
+
633
714
  return Promise.reject(res)
634
715
  }
635
716
  return res
@@ -681,49 +762,60 @@ function WOrmMongodb(opt = {}) {
681
762
  //id, del不補值, 未帶有效id者視為本筆無法處理
682
763
  let id = get(v, 'id')
683
764
 
765
+ //rest
766
+ let rest = null
767
+
684
768
  //check, 不得將無效id送進查詢條件, 因undefined經序列化為null會誤中id為null之數據
685
769
  if (!isestr(id)) {
686
- return {
770
+
771
+ //rest
772
+ rest = {
687
773
  n: 0,
688
774
  nDeleted: 0,
689
775
  ok: 0,
690
776
  err: `invalid id[${id}]`,
691
777
  }
778
+
692
779
  }
780
+ else {
693
781
 
694
- //rest
695
- let rest = null
782
+ try {
696
783
 
697
- try {
784
+ //deleteOne
785
+ let r = await collection.deleteOne({ id })
698
786
 
699
- //deleteOne
700
- let r = await collection.deleteOne({ id })
787
+ //nDeleted
788
+ let nDeleted = get(r, 'deletedCount', 0)
701
789
 
702
- //nDeleted
703
- let nDeleted = get(r, 'deletedCount', 0)
790
+ //rest, 未命中時nDeleted為0, 屬正常結果
791
+ rest = {
792
+ n: nDeleted,
793
+ nDeleted,
794
+ ok: 1,
795
+ }
704
796
 
705
- //rest, 未命中時nDeleted為0, 屬正常結果
706
- rest = {
707
- n: nDeleted,
708
- nDeleted,
709
- ok: 1,
710
797
  }
798
+ catch (err) {
711
799
 
712
- }
713
- catch (err) {
800
+ //本筆失敗不中斷整批
714
801
 
715
- //本筆失敗不中斷整批
802
+ //rest
803
+ rest = {
804
+ n: 1,
805
+ nDeleted: 0,
806
+ ok: 0,
807
+ err: getErrMsg(err),
808
+ }
716
809
 
717
- //rest
718
- rest = {
719
- n: 1,
720
- nDeleted: 0,
721
- ok: 0,
722
- err: genErrMsg(err),
723
810
  }
724
811
 
725
812
  }
726
813
 
814
+ //emit, 逐筆失敗須於該筆結果定案後發出, 每筆一次
815
+ if (rest.ok === 0) {
816
+ emitError('del', [v], rest.err)
817
+ }
818
+
727
819
  return rest
728
820
  })
729
821
 
@@ -742,7 +834,12 @@ function WOrmMongodb(opt = {}) {
742
834
  emitChange('del', data, res)
743
835
  }
744
836
 
837
+ //check
745
838
  if (isErr) {
839
+
840
+ //emit, 整批性錯誤須於reject之前發出
841
+ emitError('del', data, res)
842
+
746
843
  return Promise.reject(res)
747
844
  }
748
845
  return res
@@ -799,7 +896,12 @@ function WOrmMongodb(opt = {}) {
799
896
  emitChange('delAll', null, res)
800
897
  }
801
898
 
899
+ //check
802
900
  if (isErr) {
901
+
902
+ //emit, 整批性錯誤須於reject之前發出
903
+ emitError('delAll', null, res)
904
+
803
905
  return Promise.reject(res)
804
906
  }
805
907
  return res
@@ -808,8 +910,9 @@ function WOrmMongodb(opt = {}) {
808
910
 
809
911
  /**
810
912
  * 使用GridFS,插入數據,僅於id不存在時寫入,已存在者跳過且不覆寫
811
- * 數據物件形狀為{ id, u8a },id未給時自動產生,u8a須為Uint8Array
913
+ * 數據物件形狀為{ id, u8a },u8a須為Uint8Array
812
914
  * 註: n為輸入筆數,nInserted為實際插入筆數,全數已存在而nInserted為0屬正常結果
915
+ * 註: opt.autoGenPk為true(預設)時未帶有效id者自動產生,為false時未帶有效id即reject且同批皆不寫入
813
916
  *
814
917
  * @memberOf WOrmMongodb
815
918
  * @param {Object|Array} data 輸入數據物件或陣列,各數據物件形狀為{ id, u8a }
@@ -859,9 +962,7 @@ function WOrmMongodb(opt = {}) {
859
962
  //u8a無效屬呼叫端給值錯誤且整批函數無從逐筆回報, 故往外拋
860
963
  data = map(data, function(v, k) {
861
964
  v = { ...v }
862
- if (!isestr(v.id)) {
863
- v.id = genID()
864
- }
965
+ v = procPk(v, k)
865
966
  if (!isu8arr(v.u8a)) {
866
967
  throw new Error(`invalid data[${k}].u8a`)
867
968
  }
@@ -905,7 +1006,12 @@ function WOrmMongodb(opt = {}) {
905
1006
  emitChange('insertGfs', data, res)
906
1007
  }
907
1008
 
1009
+ //check
908
1010
  if (isErr) {
1011
+
1012
+ //emit, 整批性錯誤須於reject之前發出
1013
+ emitError('insertGfs', data, res)
1014
+
909
1015
  return Promise.reject(res)
910
1016
  }
911
1017
  return res
@@ -1010,13 +1116,19 @@ function WOrmMongodb(opt = {}) {
1010
1116
  client = null
1011
1117
  }
1012
1118
 
1119
+ //check, 查無檔案已於catch內判定為正常結果而未設isErr, 故不會誤發error事件
1013
1120
  if (isErr) {
1121
+
1122
+ //emit, 整批性錯誤須於reject之前發出
1123
+ emitError('selectByPkGfs', null, res)
1124
+
1014
1125
  return Promise.reject(res)
1015
1126
  }
1016
1127
  return res
1017
1128
  }
1018
1129
 
1019
1130
 
1131
+ //_findGfs, 內部查找函數, 其reject由delGfs與delAllGfs之catch接住並於該處發出error事件, 故本函數不自行發出
1020
1132
  async function _findGfs(find = {}, bucket) {
1021
1133
  let isErr = false
1022
1134
 
@@ -1090,54 +1202,65 @@ function WOrmMongodb(opt = {}) {
1090
1202
  //id, delGfs不補值, 未帶有效id者視為本筆無法處理
1091
1203
  let id = get(v, 'id')
1092
1204
 
1205
+ //rest
1206
+ let rest = null
1207
+
1093
1208
  //check, 判定基準與del一致
1094
1209
  if (!isestr(id)) {
1095
- return {
1210
+
1211
+ //rest
1212
+ rest = {
1096
1213
  n: 0,
1097
1214
  nDeleted: 0,
1098
1215
  ok: 0,
1099
1216
  err: `invalid id[${id}]`,
1100
1217
  }
1218
+
1101
1219
  }
1220
+ else {
1102
1221
 
1103
- //rest
1104
- let rest = null
1222
+ try {
1105
1223
 
1106
- try {
1224
+ //_findGfs
1225
+ let ltdt = await _findGfs({ filename: id }, bucket)
1107
1226
 
1108
- //_findGfs
1109
- let ltdt = await _findGfs({ filename: id }, bucket)
1227
+ //delete, 建立唯一索引後同一id至多一筆,
1228
+ //既有數據若尚存重複id則一併刪除並如實回報nDeleted
1229
+ let nDeleted = 0
1230
+ for (let vv of ltdt) {
1231
+ await bucket.delete(vv._id)
1232
+ nDeleted++
1233
+ }
1110
1234
 
1111
- //delete, 建立唯一索引後同一id至多一筆,
1112
- //既有數據若尚存重複id則一併刪除並如實回報nDeleted
1113
- let nDeleted = 0
1114
- for (let vv of ltdt) {
1115
- await bucket.delete(vv._id)
1116
- nDeleted++
1117
- }
1235
+ //rest, n為命中與否, 未命中時兩者皆為0且屬正常結果
1236
+ rest = {
1237
+ n: nDeleted > 0 ? 1 : 0,
1238
+ nDeleted,
1239
+ ok: 1,
1240
+ }
1118
1241
 
1119
- //rest, n為命中與否, 未命中時兩者皆為0且屬正常結果
1120
- rest = {
1121
- n: nDeleted > 0 ? 1 : 0,
1122
- nDeleted,
1123
- ok: 1,
1124
1242
  }
1243
+ catch (err) {
1125
1244
 
1126
- }
1127
- catch (err) {
1245
+ //本筆失敗不中斷整批
1128
1246
 
1129
- //本筆失敗不中斷整批
1247
+ //rest
1248
+ rest = {
1249
+ n: 1,
1250
+ nDeleted: 0,
1251
+ ok: 0,
1252
+ err: getErrMsg(err),
1253
+ }
1130
1254
 
1131
- //rest
1132
- rest = {
1133
- n: 1,
1134
- nDeleted: 0,
1135
- ok: 0,
1136
- err: genErrMsg(err),
1137
1255
  }
1138
1256
 
1139
1257
  }
1140
1258
 
1259
+ //emit, 逐筆失敗須於該筆結果定案後發出, 每筆一次
1260
+ if (rest.ok === 0) {
1261
+ emitError('delGfs', [v], rest.err)
1262
+ }
1263
+
1141
1264
  return rest
1142
1265
  })
1143
1266
 
@@ -1156,7 +1279,12 @@ function WOrmMongodb(opt = {}) {
1156
1279
  emitChange('delGfs', data, res)
1157
1280
  }
1158
1281
 
1282
+ //check
1159
1283
  if (isErr) {
1284
+
1285
+ //emit, 整批性錯誤須於reject之前發出
1286
+ emitError('delGfs', data, res)
1287
+
1160
1288
  return Promise.reject(res)
1161
1289
  }
1162
1290
  return res
@@ -1236,7 +1364,12 @@ function WOrmMongodb(opt = {}) {
1236
1364
  emitChange('delAllGfs', null, res)
1237
1365
  }
1238
1366
 
1367
+ //check
1239
1368
  if (isErr) {
1369
+
1370
+ //emit, 整批性錯誤須於reject之前發出
1371
+ emitError('delAllGfs', null, res)
1372
+
1240
1373
  return Promise.reject(res)
1241
1374
  }
1242
1375
  return res