w-orm-lmdb 1.0.16 → 1.0.18

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.
package/src/WOrmLmdb.mjs CHANGED
@@ -23,12 +23,30 @@ import waitFun from 'wsemi/src/waitFun.mjs'
23
23
  /**
24
24
  * 操作資料庫(LMDB)
25
25
  *
26
+ * 回傳物件為EventEmitter,除各操作函數外另發出change與error兩事件,供呼叫端於單一處集中觀察資料異動與失敗。
27
+ * 事件僅為附加通知,其所送出之資訊皆另有正規管道(操作結果經resolve、整批性錯誤經reject、逐筆失敗經該筆之err欄位),
28
+ * 故不監聽亦能取得完整資訊,且監聽與否不改變任何操作之回傳值。
29
+ *
30
+ * change事件,參數為(mode, data, res),於資料實際異動成功後發出:
31
+ * mode為操作別字串,可為'insert'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
32
+ * data為本次操作之輸入數據,delAll固定為null。
33
+ * res為本次操作之回傳結果。
34
+ * 逐筆函數以整批為單位發出一次而不逐筆發出,select與selectByPk不發出本事件。
35
+ *
36
+ * error事件,參數為(mode, data, err),於操作發生錯誤時發出:
37
+ * mode為操作別字串,可為'select'、'selectByPk'、'insert'、'save'、'del'、'delAll'。
38
+ * data為本次操作之輸入數據,無輸入數據者為null。
39
+ * err為錯誤訊息字串,內容與正規管道所送出者一致。
40
+ * 整批性錯誤於reject之前發出;逐筆失敗於該筆結果定案後發出,每筆一次。
41
+ * 註: 逐筆失敗時整批仍resolve,故收到本事件不表示該次呼叫失敗;正常結果(如查無數據、主鍵未命中、全數已存在)不發出本事件。
42
+ *
26
43
  * @class
27
44
  * @param {Object} [opt={}] 輸入設定物件,預設{}
28
45
  * @param {String} [opt.url='_db'] 輸入資料庫用資料夾字串,預設'_db'
29
46
  * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
30
47
  * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
31
48
  * @param {Boolean} [opt.useCache=false] 輸入是否使用select快取,適用於單程序操作,預設false
49
+ * @param {Boolean} [opt.autoGenPk=true] 輸入insert與save於數據未帶有效主鍵(本套件為id欄位)時是否自動產生主鍵值,預設true;給false代表主鍵改由呼叫端自備,套件不產生亦不檢查其唯一性與格式,未帶有效主鍵者將reject
32
50
  * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
33
51
  */
34
52
  function WOrmLmdb(opt = {}) {
@@ -60,6 +78,13 @@ function WOrmLmdb(opt = {}) {
60
78
  useCache = false
61
79
  }
62
80
 
81
+ //autoGenPk, 主鍵由誰產生為整個資料表之政策, 故為建構層設定, 不可於insert與save之option逐次覆寫,
82
+ //否則同一資料表將混入兩種來源之主鍵而難以追溯
83
+ let autoGenPk = get(opt, 'autoGenPk')
84
+ if (!isbol(autoGenPk)) {
85
+ autoGenPk = true
86
+ }
87
+
63
88
  //storage
64
89
  let storage = `${url}/${db}/${cl}`
65
90
  // console.log('storage',storage)
@@ -110,7 +135,30 @@ function WOrmLmdb(opt = {}) {
110
135
  })
111
136
  }
112
137
 
113
- //getErrMsg, 取錯誤訊息字串, 供逐筆結果之err欄位使用
138
+ //emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
139
+ //一律包try/catch, 令訂閱函數自身拋錯不影響本次操作之結果
140
+ let emitChange = (mode, data, res) => {
141
+ try {
142
+ ee.emit('change', mode, data, res)
143
+ }
144
+ catch (err) {
145
+ console.log(err)
146
+ }
147
+ }
148
+
149
+ //emitError, 操作發生錯誤時發出, 錯誤訊息一律轉為字串
150
+ //此try/catch除攔訂閱函數之例外外另有必要: Node之EventEmitter於'error'無監聽者時會將錯誤直接拋出,
151
+ //若不攔則同一次操作會因呼叫端有無註冊監聽而走向不同結果
152
+ let emitError = (mode, data, err) => {
153
+ try {
154
+ ee.emit('error', mode, data, getErrMsg(err))
155
+ }
156
+ catch (errEmit) {
157
+ console.log(errEmit)
158
+ }
159
+ }
160
+
161
+ //getErrMsg, 取錯誤訊息字串, 供逐筆結果之err欄位與error事件使用
114
162
  let getErrMsg = (err) => {
115
163
  let m = get(err, 'message')
116
164
  if (isestr(m)) {
@@ -119,6 +167,26 @@ function WOrmLmdb(opt = {}) {
119
167
  return String(err)
120
168
  }
121
169
 
170
+ //procPk, 依autoGenPk處理各數據之主鍵, 供insert與save共用
171
+ //autoGenPk為true時補值; 為false時不補值, 未帶有效主鍵者屬呼叫端未履行契約, 拋出為整批性錯誤,
172
+ //且檢查於任何寫入之前完成, 故不會有部份筆數已寫入而整批失敗之情形
173
+ let procPk = (data, fnName) => {
174
+ if (autoGenPk) {
175
+ return map(data, function(v) {
176
+ if (!isestr(v.id)) {
177
+ v.id = genIDSeq()
178
+ }
179
+ return v
180
+ })
181
+ }
182
+ each(data, function(v, k) {
183
+ if (!isestr(get(v, 'id'))) {
184
+ throw new Error(`can not ${fnName} by data[${k}] without valid id when autoGenPk is false`)
185
+ }
186
+ })
187
+ return data
188
+ }
189
+
122
190
  //getValue
123
191
  let getValue = async (key) => {
124
192
  let value = null
@@ -184,6 +252,10 @@ function WOrmLmdb(opt = {}) {
184
252
 
185
253
  //check
186
254
  if (isErr) {
255
+
256
+ //emit, 整批性錯誤須於reject之前發出
257
+ emitError('select', null, res)
258
+
187
259
  return Promise.reject(res)
188
260
  }
189
261
 
@@ -233,6 +305,10 @@ function WOrmLmdb(opt = {}) {
233
305
 
234
306
  //check
235
307
  if (isErr) {
308
+
309
+ //emit, 整批性錯誤須於reject之前發出
310
+ emitError('selectByPk', null, res)
311
+
236
312
  return Promise.reject(res)
237
313
  }
238
314
 
@@ -271,12 +347,7 @@ function WOrmLmdb(opt = {}) {
271
347
  }
272
348
 
273
349
  //check id
274
- data = map(data, function(v) {
275
- if (!isestr(v.id)) {
276
- v.id = genIDSeq()
277
- }
278
- return v
279
- })
350
+ data = procPk(data, 'insert')
280
351
 
281
352
  //each
282
353
  //ifNoExists, 由LMDB於同一寫交易內原子完成[檢查v.id未存在]與[寫入], 併發時同一v.id僅有一次成功,
@@ -321,19 +392,15 @@ function WOrmLmdb(opt = {}) {
321
392
 
322
393
  //emit, 於change可能須使用select, 故須放在重設快取之後
323
394
  if (!isErr) {
324
- try {
325
-
326
- //emit
327
- ee.emit('change', 'insert', data, res)
328
-
329
- }
330
- catch (err) {
331
- console.log(err)
332
- }
395
+ emitChange('insert', data, res)
333
396
  }
334
397
 
335
398
  //check
336
399
  if (isErr) {
400
+
401
+ //emit, 整批性錯誤須於reject之前發出
402
+ emitError('insert', data, res)
403
+
337
404
  return Promise.reject(res)
338
405
  }
339
406
 
@@ -373,12 +440,7 @@ function WOrmLmdb(opt = {}) {
373
440
  }
374
441
 
375
442
  //check id
376
- data = map(data, function(v) {
377
- if (!isestr(v.id)) {
378
- v.id = genIDSeq()
379
- }
380
- return v
381
- })
443
+ data = procPk(data, 'save')
382
444
 
383
445
  //pmSeries
384
446
  res = await pmSeries(data, async(v) => {
@@ -507,16 +569,15 @@ function WOrmLmdb(opt = {}) {
507
569
  //update, 不能保證插入多少, 一律重設快取
508
570
  _cache = null
509
571
 
510
- try {
511
-
512
- //emit
513
- ee.emit('change', 'insert', [v], rest)
572
+ //emit
573
+ emitChange('insert', [v], rest)
514
574
 
515
- }
516
- catch (err) {
517
- console.log(err)
518
- }
575
+ }
519
576
 
577
+ //emit, 逐筆失敗於該筆結果定案後發出, 每筆一次
578
+ //此事件之發出不表示整批失敗, 整批仍resolve, 該筆以ok為0回報
579
+ if (rest.ok === 0) {
580
+ emitError('save', [v], rest.err)
520
581
  }
521
582
 
522
583
  return rest
@@ -532,20 +593,17 @@ function WOrmLmdb(opt = {}) {
532
593
  _cache = null
533
594
 
534
595
  //emit, 於change可能須使用select, 故須放在重設快取之後
596
+ //逐筆失敗之error事件已於各筆定案時發出, 故必早於本整批change
535
597
  if (!isErr) {
536
- try {
537
-
538
- //emit
539
- ee.emit('change', 'save', data, res)
540
-
541
- }
542
- catch (err) {
543
- console.log(err)
544
- }
598
+ emitChange('save', data, res)
545
599
  }
546
600
 
547
601
  //check
548
602
  if (isErr) {
603
+
604
+ //emit, 整批性錯誤須於reject之前發出
605
+ emitError('save', data, res)
606
+
549
607
  return Promise.reject(res)
550
608
  }
551
609
 
@@ -594,38 +652,42 @@ function WOrmLmdb(opt = {}) {
594
652
  if (!isestr(id)) {
595
653
  //未給有效v.id視為該筆數據有問題而無法處理, 以ok為0並附err回報,
596
654
  //與[已給v.id但查無數據]之ok為1有別, 二者須可由ok分辨
597
- return {
655
+ //註: 此處不可直接return, 否則會跳過本回調末尾之error事件發出
656
+ rest = {
598
657
  n: 0,
599
658
  nDeleted: 0,
600
659
  ok: 0,
601
660
  err: `can not delete by invalid id[${id}]`,
602
661
  }
603
662
  }
663
+ else {
604
664
 
605
- //查找資料表內v.id
606
- let vv = await getValue(id) //不會有catch
665
+ //查找資料表內v.id
666
+ let vv = await getValue(id) //不會有catch
607
667
 
608
- //check
609
- if (iseobj(vv)) {
610
- //已存在v.id則須刪除
668
+ //check
669
+ if (iseobj(vv)) {
670
+ //已存在v.id則須刪除
611
671
 
612
- //del
613
- await client.del(id)
672
+ //del
673
+ await client.del(id)
674
+
675
+ //rest
676
+ rest = {
677
+ n: 1,
678
+ nDeleted: 1,
679
+ ok: 1,
680
+ }
614
681
 
615
- //rest
616
- rest = {
617
- n: 1,
618
- nDeleted: 1,
619
- ok: 1,
620
682
  }
683
+ else {
684
+ //不存在v.id則不刪除, 未命中故n為0
685
+ rest = {
686
+ n: 0,
687
+ nDeleted: 0,
688
+ ok: 1,
689
+ }
621
690
 
622
- }
623
- else {
624
- //不存在v.id則不刪除, 未命中故n為0
625
- rest = {
626
- n: 0,
627
- nDeleted: 0,
628
- ok: 1,
629
691
  }
630
692
 
631
693
  }
@@ -643,6 +705,12 @@ function WOrmLmdb(opt = {}) {
643
705
 
644
706
  }
645
707
 
708
+ //emit, 逐筆失敗於該筆結果定案後發出, 每筆一次
709
+ //此事件之發出不表示整批失敗, 整批仍resolve, 該筆以ok為0回報
710
+ if (rest.ok === 0) {
711
+ emitError('del', [v], rest.err)
712
+ }
713
+
646
714
  return rest
647
715
  })
648
716
 
@@ -656,20 +724,17 @@ function WOrmLmdb(opt = {}) {
656
724
  _cache = null
657
725
 
658
726
  //emit, 於change可能須使用select, 故須放在重設快取之後
727
+ //逐筆失敗之error事件已於各筆定案時發出, 故必早於本整批change
659
728
  if (!isErr) {
660
- try {
661
-
662
- //emit
663
- ee.emit('change', 'del', data, res)
664
-
665
- }
666
- catch (err) {
667
- console.log(err)
668
- }
729
+ emitChange('del', data, res)
669
730
  }
670
731
 
671
732
  //check
672
733
  if (isErr) {
734
+
735
+ //emit, 整批性錯誤須於reject之前發出
736
+ emitError('del', data, res)
737
+
673
738
  return Promise.reject(res)
674
739
  }
675
740
 
@@ -771,19 +836,15 @@ function WOrmLmdb(opt = {}) {
771
836
 
772
837
  //emit, 於change可能須使用select, 故須放在重設快取之後
773
838
  if (!isErr) {
774
- try {
775
-
776
- //emit
777
- ee.emit('change', 'delAll', null, res)
778
-
779
- }
780
- catch (err) {
781
- console.log(err)
782
- }
839
+ emitChange('delAll', null, res)
783
840
  }
784
841
 
785
842
  //check
786
843
  if (isErr) {
844
+
845
+ //emit, 整批性錯誤須於reject之前發出
846
+ emitError('delAll', null, res)
847
+
787
848
  return Promise.reject(res)
788
849
  }
789
850
 
@@ -0,0 +1,188 @@
1
+ import assert from 'assert'
2
+ import _ from 'lodash-es'
3
+ import w from 'wsemi'
4
+ import WOrm from '../src/WOrmLmdb.mjs'
5
+
6
+
7
+ describe('autoGenPk', function() {
8
+ let rt = null
9
+ let vans = {}
10
+ let vget = {}
11
+
12
+ before(async function() {
13
+
14
+ w.fsDeleteFolder('./_db_autogenpk')
15
+
16
+ let url = './_db_autogenpk'
17
+
18
+ //預設值須為true, 未帶id者自動補值
19
+ let woDef = WOrm({ url, db: 'worm', cl: 'def' })
20
+ await woDef.insert({ name: 'peter' })
21
+ let rsDef = await woDef.select()
22
+ vget[1] = _.size(rsDef)
23
+ vget[2] = w.isestr(_.get(rsDef, '0.id'))
24
+ await woDef.close()
25
+
26
+ //明確給true, 行為須與預設相同
27
+ let woTrue = WOrm({ url, db: 'worm', cl: 'ptrue', autoGenPk: true })
28
+ rt = null
29
+ // vans[3] = { n: 2, nInserted: 2, ok: 1 }
30
+ await woTrue.insert([{ name: 'a' }, { name: 'b' }])
31
+ .then(function(msg) {
32
+ rt = msg
33
+ })
34
+ .catch(function(msg) {
35
+ rt = 'reject: ' + msg.toString()
36
+ })
37
+ vget[3] = rt
38
+ vget[4] = _.size(_.filter(await woTrue.select(), function(v) {
39
+ return w.isestr(v.id)
40
+ }))
41
+ await woTrue.close()
42
+
43
+ //給false且自備id, 須正常寫入且沿用呼叫端所給之id
44
+ let woFalse = WOrm({ url, db: 'worm', cl: 'pfalse', autoGenPk: false })
45
+ rt = null
46
+ // vans[5] = { n: 2, nInserted: 2, ok: 1 }
47
+ await woFalse.insert([{ id: 'own-1', name: 'a' }, { id: 'own-2', name: 'b' }])
48
+ .then(function(msg) {
49
+ rt = msg
50
+ })
51
+ .catch(function(msg) {
52
+ rt = 'reject: ' + msg.toString()
53
+ })
54
+ vget[5] = rt
55
+ vget[6] = _.map(_.sortBy(await woFalse.select(), 'id'), 'id')
56
+
57
+ //給false且save自備id, 須正常處理
58
+ rt = null
59
+ // vans[7] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
60
+ await woFalse.save({ id: 'own-1', name: 'a2' })
61
+ .then(function(msg) {
62
+ rt = msg
63
+ })
64
+ .catch(function(msg) {
65
+ rt = 'reject: ' + msg.toString()
66
+ })
67
+ vget[7] = rt
68
+
69
+ //給false而insert未帶有效id, 須reject且不得有任何寫入
70
+ rt = null
71
+ await woFalse.insert({ name: 'no-id' })
72
+ .then(function(msg) {
73
+ rt = 'resolve: ' + JSON.stringify(msg)
74
+ })
75
+ .catch(function(msg) {
76
+ rt = 'reject'
77
+ })
78
+ vget[8] = rt
79
+
80
+ //給false而save未帶有效id, 須reject
81
+ rt = null
82
+ await woFalse.save({ name: 'no-id' })
83
+ .then(function(msg) {
84
+ rt = 'resolve: ' + JSON.stringify(msg)
85
+ })
86
+ .catch(function(msg) {
87
+ rt = 'reject'
88
+ })
89
+ vget[9] = rt
90
+
91
+ //給false而整批內僅部份未帶有效id, 須整批reject且不得有部份筆數已寫入
92
+ rt = null
93
+ await woFalse.insert([{ id: 'own-3', name: 'c' }, { name: 'no-id' }])
94
+ .then(function(msg) {
95
+ rt = 'resolve: ' + JSON.stringify(msg)
96
+ })
97
+ .catch(function(msg) {
98
+ rt = 'reject'
99
+ })
100
+ vget[10] = rt
101
+ vget[11] = _.map(_.sortBy(await woFalse.select(), 'id'), 'id')
102
+ await woFalse.close()
103
+
104
+ //del不受autoGenPk影響, 未帶有效id者為該筆問題而非整批性錯誤
105
+ let woDel = WOrm({ url, db: 'worm', cl: 'pdel', autoGenPk: false })
106
+ await woDel.insert({ id: 'd1', name: 'x' })
107
+ rt = null
108
+ await woDel.del([{ id: 'd1' }, { name: 'no-id' }])
109
+ .then(function(msg) {
110
+ rt = _.map(msg, function(v) {
111
+ return _.omit(v, 'err')
112
+ })
113
+ })
114
+ .catch(function(msg) {
115
+ rt = 'reject: ' + msg.toString()
116
+ })
117
+ vget[12] = rt
118
+ await woDel.close()
119
+
120
+ })
121
+
122
+ vans[1] = 1
123
+ it(`should get ${JSON.stringify(vans[1])} for records by insert without id in default`, async function() {
124
+ assert.strict.deepStrictEqual(vget[1], vans[1])
125
+ })
126
+
127
+ vans[2] = true
128
+ it(`should get ${JSON.stringify(vans[2])} for id generated by default`, async function() {
129
+ assert.strict.deepStrictEqual(vget[2], vans[2])
130
+ })
131
+
132
+ vans[3] = { n: 2, nInserted: 2, ok: 1 }
133
+ it(`should get ${JSON.stringify(vans[3])} for insert without id by autoGenPk=true`, async function() {
134
+ assert.strict.deepStrictEqual(vget[3], vans[3])
135
+ })
136
+
137
+ vans[4] = 2
138
+ it(`should get ${JSON.stringify(vans[4])} for records with generated id by autoGenPk=true`, async function() {
139
+ assert.strict.deepStrictEqual(vget[4], vans[4])
140
+ })
141
+
142
+ vans[5] = { n: 2, nInserted: 2, ok: 1 }
143
+ it(`should get ${JSON.stringify(vans[5])} for insert with own id by autoGenPk=false`, async function() {
144
+ assert.strict.deepStrictEqual(vget[5], vans[5])
145
+ })
146
+
147
+ vans[6] = ['own-1', 'own-2']
148
+ it(`should get ${JSON.stringify(vans[6])} for keeping own id by autoGenPk=false`, async function() {
149
+ assert.strict.deepStrictEqual(vget[6], vans[6])
150
+ })
151
+
152
+ vans[7] = [{ n: 1, nInserted: 0, nModified: 1, ok: 1 }]
153
+ it(`should get ${JSON.stringify(vans[7])} for save with own id by autoGenPk=false`, async function() {
154
+ assert.strict.deepStrictEqual(vget[7], vans[7])
155
+ })
156
+
157
+ //autoGenPk為false而未帶有效主鍵屬呼叫端未履行契約, 須為整批性錯誤而非該筆ok為0
158
+ vans[8] = 'reject'
159
+ it(`should get ${JSON.stringify(vans[8])} for insert without id by autoGenPk=false`, async function() {
160
+ assert.strict.deepStrictEqual(vget[8], vans[8])
161
+ })
162
+
163
+ vans[9] = 'reject'
164
+ it(`should get ${JSON.stringify(vans[9])} for save without id by autoGenPk=false`, async function() {
165
+ assert.strict.deepStrictEqual(vget[9], vans[9])
166
+ })
167
+
168
+ vans[10] = 'reject'
169
+ it(`should get ${JSON.stringify(vans[10])} for insert by array partially without id by autoGenPk=false`, async function() {
170
+ assert.strict.deepStrictEqual(vget[10], vans[10])
171
+ })
172
+
173
+ //檢查須於任何寫入之前完成, 故整批reject時同批之有效筆數亦不得被寫入
174
+ vans[11] = ['own-1', 'own-2']
175
+ it(`should get ${JSON.stringify(vans[11])} for no partial writing when batch rejected`, async function() {
176
+ assert.strict.deepStrictEqual(vget[11], vans[11])
177
+ })
178
+
179
+ //del不受autoGenPk影響
180
+ vans[12] = [
181
+ { n: 1, nDeleted: 1, ok: 1 },
182
+ { n: 0, nDeleted: 0, ok: 0 },
183
+ ]
184
+ it(`should get ${JSON.stringify(vans[12])} for del not affected by autoGenPk=false`, async function() {
185
+ assert.strict.deepStrictEqual(vget[12], vans[12])
186
+ })
187
+
188
+ })