w-orm-lmdb 1.0.18 → 1.0.20

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="WOrmLmdb.html">WOrmLmdb</a><ul class='methods'><li data-type='method'><a href="WOrmLmdb.html#.del">del</a></li><li data-type='method'><a href="WOrmLmdb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmLmdb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmLmdb.html#.save">save</a></li><li data-type='method'><a href="WOrmLmdb.html#.select">select</a></li><li data-type='method'><a href="WOrmLmdb.html#.selectByPk">selectByPk</a></li></ul></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmLmdb.html">WOrmLmdb</a><ul class='methods'><li data-type='method'><a href="WOrmLmdb.html#.del">del</a></li><li data-type='method'><a href="WOrmLmdb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmLmdb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmLmdb.html#.insertBulk">insertBulk</a></li><li data-type='method'><a href="WOrmLmdb.html#.save">save</a></li><li data-type='method'><a href="WOrmLmdb.html#.select">select</a></li><li data-type='method'><a href="WOrmLmdb.html#.selectByPk">selectByPk</a></li></ul></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -45,7 +45,7 @@
45
45
 
46
46
  <section>
47
47
  <article>
48
- <pre class="prettyprint source linenums"><code>import { open } from 'lmdb'
48
+ <pre class="prettyprint source linenums"><code>import { open, ABORT } from 'lmdb'
49
49
  // import mingo from 'mingo' //mingo內未更新import寫法, 會導致ERR_UNSUPPORTED_DIR_IMPORT, 故須改用require引入使用
50
50
  import mingo from './reqMingo.js'
51
51
  import size from 'lodash-es/size.js'
@@ -75,13 +75,13 @@ import waitFun from 'wsemi/src/waitFun.mjs'
75
75
  * 故不監聽亦能取得完整資訊,且監聽與否不改變任何操作之回傳值。
76
76
  *
77
77
  * change事件,參數為(mode, data, res),於資料實際異動成功後發出:
78
- * mode為操作別字串,可為'insert'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
78
+ * mode為操作別字串,可為'insert'、'insertBulk'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
79
79
  * data為本次操作之輸入數據,delAll固定為null。
80
80
  * res為本次操作之回傳結果。
81
81
  * 逐筆函數以整批為單位發出一次而不逐筆發出,select與selectByPk不發出本事件。
82
82
  *
83
83
  * error事件,參數為(mode, data, err),於操作發生錯誤時發出:
84
- * mode為操作別字串,可為'select'、'selectByPk'、'insert'、'save'、'del'、'delAll'。
84
+ * mode為操作別字串,可為'select'、'selectByPk'、'insert'、'insertBulk'、'save'、'del'、'delAll'。
85
85
  * data為本次操作之輸入數據,無輸入數據者為null。
86
86
  * err為錯誤訊息字串,內容與正規管道所送出者一致。
87
87
  * 整批性錯誤於reject之前發出;逐筆失敗於該筆結果定案後發出,每筆一次。
@@ -173,13 +173,25 @@ function WOrmLmdb(opt = {}) {
173
173
  }
174
174
 
175
175
  //waitOpen, 等待client開啟, 因getRange與get於client關閉時會拋錯
176
+ //closed為終態: LMDB env一旦關閉不會自行回到open, 續等必然逾時, 故直接拋出而非空轉至逾時
177
+ //各函數入口已有procClosed守門, 此處之終態判斷為縱深防禦, 攔[操作進行中被close]之競態
178
+ //等待參數收斂: 開啟中為毫秒級過渡, 50次x200ms=10秒已極寬裕, 不使非終態之等待拖至waitFun預設之200秒
176
179
  let waitOpen = async () => {
180
+ if (client.status === 'closed') {
181
+ throw new Error('client is closed, instance can not be used after close()')
182
+ }
177
183
  await waitFun(() => {
178
- if (client.status === 'closed') {
179
- console.log(`client.status[${client.status}], level is closed`)
180
- }
181
184
  return client.status === 'open'
182
- })
185
+ }, { attemptNum: 50, timeInterval: 200 })
186
+ }
187
+
188
+ //procClosed, 各函數入口之終態守門: close()後再操作屬整批性錯誤, 立即reject令誤用當場可見
189
+ //不可靜默回正常空值(如null或未命中), 否則[已關閉]與[查無資料]同形,
190
+ //去重類呼叫端將把每筆判為未存在而fail-open, 整批重送下游昂貴動作
191
+ let procClosed = (mode, data) => {
192
+ let msg = `can not ${mode} by closed client, instance can not be used after close()`
193
+ emitError(mode, data, msg)
194
+ return Promise.reject(new Error(msg))
183
195
  }
184
196
 
185
197
  //emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
@@ -259,6 +271,11 @@ function WOrmLmdb(opt = {}) {
259
271
  * @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
260
272
  */
261
273
  async function select(find = {}) {
274
+
275
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
276
+ if (client.status === 'closed') {
277
+ return procClosed('select', null)
278
+ }
262
279
  let isErr = false
263
280
  let res = null
264
281
 
@@ -318,6 +335,11 @@ function WOrmLmdb(opt = {}) {
318
335
  * @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵則回傳null,reject回傳錯誤訊息
319
336
  */
320
337
  async function selectByPk(pk) {
338
+
339
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
340
+ if (client.status === 'closed') {
341
+ return procClosed('selectByPk', null)
342
+ }
321
343
  let isErr = false
322
344
  let res = null
323
345
 
@@ -367,13 +389,30 @@ function WOrmLmdb(opt = {}) {
367
389
  *
368
390
  * @memberOf WOrmLmdb
369
391
  * @param {Object|Array} data 輸入數據物件或陣列
370
- * @returns {Promise} 回傳Promise,resolve回傳插入結果物件{n,nInserted,ok},n為輸入筆數、nInserted為實際插入筆數,reject回傳錯誤訊息
392
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
393
+ * @param {Boolean} [option.returnList=false] 輸入是否改回傳逐筆結果布林值,預設false。給true時回傳與輸入等長保序之陣列,各筆為{n,nInserted,ok},nInserted為1即該筆為新增;聚合計數只回答有幾筆是新的,逐筆結果方能回答是哪幾筆,供下游僅對新資料執行昂貴動作。回傳形式由呼叫點靜態決定,共用之結果處理程式碼不可跨不同取值之呼叫點混用
394
+ * @returns {Promise} 回傳Promise,resolve依returnList回傳插入結果:預設回傳聚合物件{n,nInserted,ok},n為輸入筆數、nInserted為實際插入筆數;returnList為true時回傳逐筆陣列,各筆n恆為1(主鍵命中或經插入)、ok恆為1(insert之錯誤皆屬整批性而reject,不進逐筆);reject回傳錯誤訊息
371
395
  */
372
- async function insert(data) {
396
+ async function insert(data, option = {}) {
397
+
398
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
399
+ if (client.status === 'closed') {
400
+ return procClosed('insert', data)
401
+ }
373
402
  let isErr = false
374
403
 
404
+ //returnList
405
+ let returnList = get(option, 'returnList')
406
+ if (!isbol(returnList)) {
407
+ returnList = false
408
+ }
409
+
375
410
  //check
376
411
  if (!iseobj(data) &amp;&amp; !isearr(data)) {
412
+ if (returnList) {
413
+ //輸入無效, 對齊save與del之T5規定回傳空陣列
414
+ return []
415
+ }
377
416
  return {
378
417
  n: 0,
379
418
  nInserted: 0,
@@ -422,9 +461,122 @@ function WOrmLmdb(opt = {}) {
422
461
  })
423
462
 
424
463
  //res
464
+ if (returnList) {
465
+ //逐筆結果, 與輸入等長保序, 元素沿用逐筆結果之家族形狀(同save與del之元素)
466
+ //n恆為1(主鍵命中既有或經插入而產生), ok恆為1(insert之錯誤皆屬整批性而reject, 不進逐筆),
467
+ //資訊由nInserted承載, filter(v=>v.nInserted===1)之長度必等於聚合模式之nInserted
468
+ res = map(ltdone, function(done) {
469
+ return {
470
+ n: 1,
471
+ nInserted: done ? 1 : 0,
472
+ ok: 1,
473
+ }
474
+ })
475
+ }
476
+ else {
477
+ res = {
478
+ n: nAll,
479
+ nInserted: nPush,
480
+ ok: 1,
481
+ }
482
+ }
483
+
484
+ }
485
+ catch (err) {
486
+ isErr = true
487
+ res = err
488
+ }
489
+
490
+ //update, 不能保證插入多少, 一律重設快取
491
+ _cache = null
492
+
493
+ //emit, 於change可能須使用select, 故須放在重設快取之後
494
+ if (!isErr) {
495
+ emitChange('insert', data, res)
496
+ }
497
+
498
+ //check
499
+ if (isErr) {
500
+
501
+ //emit, 整批性錯誤須於reject之前發出
502
+ emitError('insert', data, res)
503
+
504
+ return Promise.reject(res)
505
+ }
506
+
507
+ return res
508
+ }
509
+
510
+ /**
511
+ * 批次插入數據,全批視為一個單位,全部插入成功或一筆都不寫入
512
+ * 註: 本函數非insert之加速版,兩者衝突政策不同。insert於主鍵已存在時跳過該筆而整批ok為1,
513
+ * 本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
514
+ * 註: 於本套件不會較insert快,因insert本即以Promise.all一次送出全部條件寫入而非逐筆await,
515
+ * 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
516
+ *
517
+ * @memberOf WOrmLmdb
518
+ * @param {Object|Array} data 輸入數據物件或陣列
519
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果物件{n,nInserted,ok},n為輸入筆數、nInserted成功時恆等於n,任一筆主鍵已存在則reject回傳錯誤訊息
520
+ */
521
+ async function insertBulk(data) {
522
+
523
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
524
+ if (client.status === 'closed') {
525
+ return procClosed('insertBulk', data)
526
+ }
527
+ let isErr = false
528
+
529
+ //check
530
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
531
+ return {
532
+ n: 0,
533
+ nInserted: 0,
534
+ ok: 1,
535
+ }
536
+ }
537
+
538
+ //cloneDeep, 與外部數據脫勾
539
+ data = cloneDeep(data)
540
+
541
+ //res
542
+ let res = null
543
+ try {
544
+
545
+ //check
546
+ if (!isarr(data)) {
547
+ data = [data]
548
+ }
549
+
550
+ //check id
551
+ data = procPk(data, 'insertBulk')
552
+
553
+ //nAll
554
+ let nAll = size(data)
555
+
556
+ //childTransaction, 全有全無由交易回滾保證
557
+ //註: 非同步之client.transaction於中止時並不回滾, 實測其內之put仍會落盤, 故此處須用childTransaction
558
+ //註: 交易內之client.get讀得到同一交易稍早之put, 故同批含重複主鍵者亦會於此被偵測為衝突
559
+ //註: 存在與否採鍵層判定, 與insert之ifNoExists一致
560
+ let pkConflict = null
561
+ await client.childTransaction(() => {
562
+ for (let v of data) {
563
+ if (client.get(v.id) !== undefined) {
564
+ pkConflict = v.id
565
+ return ABORT
566
+ }
567
+ client.put(v.id, v)
568
+ }
569
+ })
570
+
571
+ //check, 衝突則整批視為失敗, 交易已回滾故無任何寫入
572
+ if (isestr(pkConflict)) {
573
+ throw new Error(`can not insertBulk by existed id[${pkConflict}]`)
574
+ }
575
+
576
+ //res, 未衝突則全數插入, 故nInserted恆等於n
425
577
  res = {
426
578
  n: nAll,
427
- nInserted: nPush,
579
+ nInserted: nAll,
428
580
  ok: 1,
429
581
  }
430
582
 
@@ -439,14 +591,14 @@ function WOrmLmdb(opt = {}) {
439
591
 
440
592
  //emit, 於change可能須使用select, 故須放在重設快取之後
441
593
  if (!isErr) {
442
- emitChange('insert', data, res)
594
+ emitChange('insertBulk', data, res)
443
595
  }
444
596
 
445
597
  //check
446
598
  if (isErr) {
447
599
 
448
600
  //emit, 整批性錯誤須於reject之前發出
449
- emitError('insert', data, res)
601
+ emitError('insertBulk', data, res)
450
602
 
451
603
  return Promise.reject(res)
452
604
  }
@@ -464,6 +616,11 @@ function WOrmLmdb(opt = {}) {
464
616
  * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之儲存結果陣列,各筆為{n,nInserted,nModified,ok},n為主鍵命中筆數,單筆失敗者ok為0並附err,reject回傳錯誤訊息
465
617
  */
466
618
  async function save(data, option = {}) {
619
+
620
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
621
+ if (client.status === 'closed') {
622
+ return procClosed('save', data)
623
+ }
467
624
  let isErr = false
468
625
 
469
626
  //check
@@ -665,6 +822,11 @@ function WOrmLmdb(opt = {}) {
665
822
  * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列,各筆為{n,nDeleted,ok},n為主鍵命中筆數,單筆失敗或未給有效id者ok為0並附err,reject回傳錯誤訊息
666
823
  */
667
824
  async function del(data) {
825
+
826
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
827
+ if (client.status === 'closed') {
828
+ return procClosed('del', data)
829
+ }
668
830
  let isErr = false
669
831
 
670
832
  //check
@@ -796,6 +958,11 @@ function WOrmLmdb(opt = {}) {
796
958
  * @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},n與nDeleted同為實際刪除筆數,reject回傳錯誤訊息
797
959
  */
798
960
  async function delAll(find = {}) {
961
+
962
+ //check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
963
+ if (client.status === 'closed') {
964
+ return procClosed('delAll', null)
965
+ }
799
966
  let isErr = false
800
967
 
801
968
  //res
@@ -898,7 +1065,8 @@ function WOrmLmdb(opt = {}) {
898
1065
  return res
899
1066
  }
900
1067
 
901
- //close, 關閉內部LMDB env並釋放檔案鎖, close後該實例即報廢, 須重新new WOrmLmdb
1068
+ //close, 關閉內部LMDB env並釋放檔案鎖, close後該實例即報廢, 須重新new WOrmLmdb,
1069
+ //再呼叫任何操作函數將立即reject(訊息明示closed), 不與[查無資料]同形
902
1070
  let close = async () => {
903
1071
  if (client &amp;&amp; client.status !== 'closed') {
904
1072
  await client.close() //lmdb root database之close(), 回傳Promise
@@ -909,6 +1077,7 @@ function WOrmLmdb(opt = {}) {
909
1077
  ee.select = select
910
1078
  ee.selectByPk = selectByPk
911
1079
  ee.insert = insert
1080
+ ee.insertBulk = insertBulk
912
1081
  ee.save = save
913
1082
  ee.del = del
914
1083
  ee.delAll = delAll
@@ -933,7 +1102,7 @@ export default WOrmLmdb
933
1102
  <br class="clear">
934
1103
 
935
1104
  <footer>
936
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 08:38:27 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1105
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 17:58:14 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
937
1106
  </footer>
938
1107
 
939
1108
  <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="WOrmLmdb.html">WOrmLmdb</a><ul class='methods'><li data-type='method'><a href="WOrmLmdb.html#.del">del</a></li><li data-type='method'><a href="WOrmLmdb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmLmdb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmLmdb.html#.save">save</a></li><li data-type='method'><a href="WOrmLmdb.html#.select">select</a></li><li data-type='method'><a href="WOrmLmdb.html#.selectByPk">selectByPk</a></li></ul></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmLmdb.html">WOrmLmdb</a><ul class='methods'><li data-type='method'><a href="WOrmLmdb.html#.del">del</a></li><li data-type='method'><a href="WOrmLmdb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmLmdb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmLmdb.html#.insertBulk">insertBulk</a></li><li data-type='method'><a href="WOrmLmdb.html#.save">save</a></li><li data-type='method'><a href="WOrmLmdb.html#.select">select</a></li><li data-type='method'><a href="WOrmLmdb.html#.selectByPk">selectByPk</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 08:38:27 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 17:58:14 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
@@ -157,10 +157,44 @@ async function test() {
157
157
  console.log('del by data without id catch', msg)
158
158
  })
159
159
 
160
+ //insertBulk, 全批視為一個單位, 無衝突時nInserted恆等於n
161
+ await wo.insertBulk([{ id: 'id-bulk1', name: 'bulk1' }, { id: 'id-bulk2', name: 'bulk2' }])
162
+ .then(function(msg) {
163
+ console.log('insertBulk then', msg)
164
+ })
165
+ .catch(function(msg) {
166
+ console.log('insertBulk catch', msg)
167
+ })
168
+
169
+ //insertBulk by data with existed id, 非insert之加速版而係衝突政策不同
170
+ //insert於主鍵已存在時跳過該筆而整批ok為1, insertBulk則整批reject且不寫入任何一筆
171
+ await wo.insertBulk([{ id: 'id-bulk3', name: 'bulk3' }, { id: 'id-peter', name: 'conflict' }])
172
+ .then(function(msg) {
173
+ console.log('insertBulk by data with existed id then', msg)
174
+ })
175
+ .catch(function(msg) {
176
+ console.log('insertBulk by data with existed id catch', msg.toString())
177
+ })
178
+
179
+ //select all, 可見id-bulk3因整批reject而未寫入
180
+ let sb2 = await wo.select()
181
+ console.log('ids after insertBulk', _.map(_.sortBy(sb2, 'id'), 'id'))
182
+
183
+ //insert by returnList, 回傳與輸入等長保序之逐筆結果, nInserted為1即該筆為新增
184
+ //聚合計數只回答有幾筆是新的, 逐筆結果方能回答是哪幾筆, 供下游僅對新資料執行昂貴動作
185
+ let rl = await wo.insert([{ id: 'id-peter', name: 'dup' }, { id: 'id-new1', name: 'new1' }], { returnList: true })
186
+ console.log('insert by returnList', rl)
187
+
188
+ //filter, 以逐筆結果對位取出新增之數據
189
+ let fresh = [{ id: 'id-peter', name: 'dup' }, { id: 'id-new1', name: 'new1' }].filter(function(v, i) {
190
+ return rl[i].nInserted === 1
191
+ })
192
+ console.log('fresh by returnList', _.map(fresh, 'id'))
193
+
160
194
  }
161
195
  test()
162
196
  // change delAll
163
- // delAll then { n: 2, nDeleted: 2, ok: 1 }
197
+ // delAll then { n: 5, nDeleted: 5, ok: 1 }
164
198
  // change insert
165
199
  // insert then { n: 3, nInserted: 3, ok: 1 }
166
200
  // change save
@@ -208,6 +242,14 @@ test()
208
242
  // error del can not delete by invalid id[]
209
243
  // change del
210
244
  // del by data without id then [ { n: 0, nDeleted: 0, ok: 0, err: 'can not delete by invalid id[]' } ]
245
+ // change insertBulk
246
+ // insertBulk then { n: 2, nInserted: 2, ok: 1 }
247
+ // error insertBulk can not insertBulk by existed id[id-peter]
248
+ // insertBulk by data with existed id catch Error: can not insertBulk by existed id[id-peter]
249
+ // ids after insertBulk [ 'id-bulk1', 'id-bulk2', 'id-peter', 'id-rosemary' ]
250
+ // change insert
251
+ // insert by returnList [ { n: 1, nInserted: 0, ok: 1 }, { n: 1, nInserted: 1, ok: 1 } ]
252
+ // fresh by returnList [ 'id-new1' ]
211
253
 
212
254
 
213
255
  //node g-basic.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "w-orm-lmdb",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "main": "dist/w-orm-lmdb.umd.js",
5
5
  "dependencies": {
6
6
  "lmdb": "^3.5.6",