w-orm-lowdb 1.0.12 → 1.0.14

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="WOrmLowdb.html">WOrmLowdb</a><ul class='methods'><li data-type='method'><a href="WOrmLowdb.html#.delAllCore">delAllCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.delCore">delCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.insertCore">insertCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.saveCore">saveCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.selectCore">selectCore</a></li></ul></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmLowdb.html">WOrmLowdb</a><ul class='methods'><li data-type='method'><a href="WOrmLowdb.html#.delAllCore">delAllCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.delCore">delCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.insertBulkCore">insertBulkCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.insertCore">insertCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.saveCore">saveCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.selectByPkCore">selectByPkCore</a></li><li data-type='method'><a href="WOrmLowdb.html#.selectCore">selectCore</a></li><li data-type='method'><a href="WOrmLowdb.html#~serialize">serialize</a></li></ul></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -45,783 +45,1072 @@
45
45
 
46
46
  <section>
47
47
  <article>
48
- <pre class="prettyprint source linenums"><code>import path from 'path'
49
- import events from 'events'
50
- import { Low } from 'lowdb'
51
- import { JSONFile } from 'lowdb/node'
52
- import { Query } from 'mingo'
53
- import get from 'lodash-es/get.js'
54
- import each from 'lodash-es/each.js'
55
- import map from 'lodash-es/map.js'
56
- import merge from 'lodash-es/merge.js'
57
- import isEqual from 'lodash-es/isEqual.js'
58
- import size from 'lodash-es/size.js'
59
- import cloneDeep from 'lodash-es/cloneDeep.js'
60
- import genID from 'wsemi/src/genID.mjs'
61
- import pmSeries from 'wsemi/src/pmSeries.mjs'
62
- import isarr from 'wsemi/src/isarr.mjs'
63
- import isbol from 'wsemi/src/isbol.mjs'
64
- import isestr from 'wsemi/src/isestr.mjs'
65
- import iseobj from 'wsemi/src/iseobj.mjs'
66
- import isearr from 'wsemi/src/isearr.mjs'
67
- import haskey from 'wsemi/src/haskey.mjs'
68
- import arrPullAt from 'wsemi/src/arrPullAt.mjs'
69
- import waitFun from 'wsemi/src/waitFun.mjs'
70
-
71
-
72
- //kpGlock
73
- let kpGlock = {}
74
-
75
-
76
- /**
77
- * 操作資料庫(lowdb)
78
- *
79
- * @class
80
- * @param {Object} [opt={}] 輸入設定物件,預設{}
81
- * @param {String} [opt.url='./db.json'] 輸入資料庫位置字串,預設'./db.json'
82
- * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
83
- * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
84
- * @param {Boolean} [opt.useCache=false] 輸入是否使用select快取,適用於單程序操作,預設false
85
- * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
86
- */
87
- function WOrmLowdb(opt = {}) {
88
-
89
- //_cache
90
- let _cache = null
91
-
92
- //url
93
- let url = get(opt, 'url')
94
- if (!isestr(url)) {
95
- url = './db.json'
96
- }
97
-
98
- //db
99
- let db = get(opt, 'db')
100
- if (!isestr(db)) {
101
- db = 'worm'
102
- }
103
-
104
- //cl
105
- let cl = get(opt, 'cl')
106
- if (!isestr(cl)) {
107
- cl = 'test'
108
- }
109
-
110
- //useCache
111
- let useCache = get(opt, 'useCache')
112
- if (!isbol(useCache)) {
113
- useCache = false
114
- }
115
-
116
- //key
117
- let key = `${db}:${cl}`
118
-
119
- //adapter
120
- let adapter = new JSONFile(url)
121
-
122
- //lowdb
123
- let lowdb = new Low(adapter, {})
124
-
125
- // //default, 此處偵測會失效, 因操作函數內會通過lowdb.read()初始化, 預設創建空陣列會被取代
126
- // if (!haskey(lowdb.data, key)) {
127
- // lowdb.data[key] = []
128
- // }
129
-
130
- //ee
131
- let ee = new events.EventEmitter()
132
-
133
- //gkey, 基於url也就是以檔案路徑為準, 同檔案路徑須使用全域鎖
134
- let gkey = path.resolve(url)
135
-
136
- //全域鎖初始化, 一次僅能執行一種操作函數, 使各類操作url(db.json)時能為獨占模式
137
- //select理論上是不用鎖, 但因為操作上db.jon.tmp更名回db.json時似乎非rename而是串流寫入, 導致有機會撈到不完整json數據進而出錯, 故還是得要上鎖以策安全
138
- if (!haskey(kpGlock, gkey)) {
139
- kpGlock[gkey] = false
140
- }
141
-
142
- //getData
143
- let getData = async() => {
144
-
145
- //check
146
- if (useCache &amp;&amp; isarr(_cache)) {
147
- return cloneDeep(_cache) //與外部使用數據脫勾
148
- }
149
-
150
- //read
151
- await lowdb.read()
152
-
153
- //default, 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
154
- if (!haskey(lowdb.data, key)) {
155
- lowdb.data[key] = []
156
- }
157
-
158
- //ltdt
159
- let ltdt = cloneDeep(lowdb.data[key]) //數據為lowdb內部的記憶體狀態本體, 須cloneDeep與外部使用數據脫勾
160
-
161
- if (useCache) {
162
- _cache = ltdt
163
- return cloneDeep(ltdt) //與外部使用數據脫勾
164
- }
165
- return ltdt
166
- }
167
-
168
- /**
169
- * 查詢數據
170
- *
171
- * @memberOf WOrmLowdb
172
- * @param {Object} [find={}] 輸入查詢條件物件
173
- * @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
174
- */
175
- async function selectCore(find = {}) {
176
- let isErr = false
177
-
178
- //res
179
- let res = null
180
- try {
181
-
182
- //ltdt
183
- let ltdt = await getData()
184
-
185
- //filter
186
- if (iseobj(find)) {
187
-
188
- //q
189
- let q = new Query(find)
190
- // console.log('q', q)
191
-
192
- //find
193
- res = q.find(ltdt).all()
194
- // console.log('res', res)
195
-
196
- }
197
- else {
198
- res = ltdt
199
- }
200
-
201
- }
202
- catch (err) {
203
- isErr = true
204
- res = err
205
- }
206
-
207
- if (isErr) {
208
- return Promise.reject(res)
209
- }
210
- return res
211
- }
212
- async function select(find = {}) {
213
- if (kpGlock[gkey]) {
214
- await waitFun(() => {
215
- return !kpGlock[gkey]
216
- })
217
- }
218
- kpGlock[gkey] = true
219
- return await selectCore(find)
220
- .finally(() => {
221
- kpGlock[gkey] = false
222
- })
223
- }
224
-
225
- /**
226
- * 插入數據,插入同樣數據會自動產生不同_id,故insert前需自行判斷有無重複
227
- *
228
- * @memberOf WOrmLowdb
229
- * @param {Object|Array} data 輸入數據物件或陣列
230
- * @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
231
- */
232
- async function insertCore(data) {
233
- let isErr = false
234
-
235
- //check
236
- if (!iseobj(data) &amp;&amp; !isearr(data)) {
237
- return {
238
- n: 0,
239
- nInserted: 0,
240
- ok: 1,
241
- }
242
- }
243
-
244
- //cloneDeep, 與外部數據脫勾
245
- data = cloneDeep(data)
246
-
247
- //res
248
- let res = null
249
- try {
250
-
251
- //check
252
- if (!isarr(data)) {
253
- data = [data]
254
- }
255
-
256
- //check id
257
- data = map(data, function(v) {
258
- if (!isestr(v.id)) {
259
- v.id = genID()
260
- }
261
- return v
262
- })
263
-
264
- //read
265
- await lowdb.read()
266
-
267
- //default, 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
268
- if (!haskey(lowdb.data, key)) {
269
- lowdb.data[key] = []
270
- }
271
-
272
- //kp
273
- let kp = {}
274
- each(lowdb.data[key], (v, k) => {
275
- kp[v.id] = { k, v }
276
- })
277
-
278
- //each
279
- let nAll = size(data)
280
- let nPush = 0
281
- let b = false
282
- each(data, (v) => {
283
-
284
- //check
285
- if (!haskey(kp, v.id)) {
286
- //未存在v.id
287
-
288
- //push
289
- lowdb.data[key].push(v)
290
-
291
- nPush++
292
- b = true
293
- }
294
- else {
295
- //已存在v.id則不push
296
- }
297
-
298
- })
299
-
300
- //write
301
- if (b) {
302
- await lowdb.write()
303
- }
304
-
305
- //res
306
- res = {
307
- n: nAll,
308
- nInserted: nPush,
309
- ok: 1,
310
- }
311
-
312
- }
313
- catch (err) {
314
- isErr = true
315
- res = err
316
- }
317
-
318
- //update, 不能保證插入多少, 一律重設快取
319
- _cache = null
320
-
321
- //emit, 於change可能須使用select, 故須放在重設快取之後
322
- if (!isErr) {
323
- try {
324
-
325
- //emit
326
- ee.emit('change', 'insert', data, res)
327
-
328
- }
329
- catch (err) {
330
- console.log(err)
331
- }
332
- }
333
-
334
- if (isErr) {
335
- return Promise.reject(res)
336
- }
337
- return res
338
- }
339
- async function insert(data) {
340
- if (kpGlock[gkey]) {
341
- await waitFun(() => {
342
- return !kpGlock[gkey]
343
- })
344
- }
345
- kpGlock[gkey] = true
346
- return await insertCore(data)
347
- .finally(() => {
348
- kpGlock[gkey] = false
349
- })
350
- }
351
-
352
- /**
353
- * 儲存數據
354
- *
355
- * @memberOf WOrmLowdb
356
- * @param {Object|Array} data 輸入數據物件或陣列
357
- * @param {Object} [option={}] 輸入設定物件,預設為{}
358
- * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
359
- * @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
360
- */
361
- async function saveCore(data, option = {}) {
362
- let isErr = false
363
-
364
- //check
365
- if (!iseobj(data) &amp;&amp; !isearr(data)) {
366
- return []
367
- }
368
-
369
- //cloneDeep, 與外部數據脫勾
370
- data = cloneDeep(data)
371
-
372
- //autoInsert
373
- let autoInsert = get(option, 'autoInsert', true)
374
-
375
- //res
376
- let res = null
377
- try {
378
-
379
- //check
380
- if (!isarr(data)) {
381
- data = [data]
382
- }
383
-
384
- //check id
385
- data = map(data, function(v) {
386
- if (!isestr(v.id)) {
387
- v.id = genID()
388
- }
389
- return v
390
- })
391
-
392
- //read
393
- await lowdb.read()
394
-
395
- //default, 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
396
- if (!haskey(lowdb.data, key)) {
397
- lowdb.data[key] = []
398
- }
399
-
400
- //kp
401
- let kp = {}
402
- each(lowdb.data[key], (v, k) => {
403
- kp[v.id] = { k, v }
404
- })
405
-
406
- //pmSeries
407
- let b = false
408
- res = await pmSeries(data, async(v) => {
409
-
410
- //rest
411
- let rest = null
412
-
413
- //查找資料表內v.id
414
- let r = get(kp, v.id, null)
415
-
416
- //existed
417
- let existed = iseobj(r)
418
-
419
- //check
420
- if (existed) {
421
- //已存在v.id
422
- if (isEqual(v, r.v)) {
423
- //內容相同不更新
424
- }
425
- else {
426
- //內容不同須更新
427
-
428
- //merge
429
- lowdb.data[key][r.k] = merge(lowdb.data[key][r.k], v)
430
-
431
- rest = { update: true }
432
- b = true
433
- }
434
- }
435
- else {
436
- //內容不存在
437
- }
438
-
439
- //rest
440
- if (iseobj(rest)) {
441
- rest = {
442
- n: 1,
443
- nModified: 1,
444
- ok: 1,
445
- }
446
- }
447
- else {
448
- rest = {
449
- n: 0,
450
- nModified: 0,
451
- ok: 1,
452
- }
453
- }
454
- //rest.n === 0:
455
- // 內容相同不更新, 不須update
456
- // 內容不存在, 若autoInsert則須insert
457
-
458
- //autoInsert
459
- if (autoInsert &amp;&amp; rest.n === 0 &amp;&amp; !existed) {
460
- let k = size(lowdb.data[key])
461
- lowdb.data[key].push(v)
462
- kp[v.id] = {
463
- k,
464
- v: lowdb.data[key][k],
465
- }
466
- b = true
467
- rest = {
468
- n: 1,
469
- nInserted: 1,
470
- ok: 1,
471
- }
472
- }
473
-
474
- return rest
475
- })
476
-
477
- //write
478
- if (b) {
479
- await lowdb.write()
480
- }
481
-
482
- }
483
- catch (err) {
484
- isErr = true
485
- res = err
486
- }
487
-
488
- //update, 不能保證變更多少, 一律重設快取
489
- _cache = null
490
-
491
- //emit, 於change可能須使用select, 故須放在重設快取之後
492
- if (!isErr) {
493
- try {
494
-
495
- //emit
496
- ee.emit('change', 'save', data, res)
497
-
498
- }
499
- catch (err) {
500
- console.log(err)
501
- }
502
- }
503
-
504
- if (isErr) {
505
- return Promise.reject(res)
506
- }
507
- return res
508
- }
509
- async function save(data, option = {}) {
510
- if (kpGlock[gkey]) {
511
- await waitFun(() => {
512
- return !kpGlock[gkey]
513
- })
514
- }
515
- kpGlock[gkey] = true
516
- return await saveCore(data, option)
517
- .finally(() => {
518
- kpGlock[gkey] = false
519
- })
520
- }
521
-
522
- /**
523
- * 刪除數據
524
- *
525
- * @memberOf WOrmLowdb
526
- * @param {Object|Array} data 輸入數據物件或陣列
527
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
528
- */
529
- async function delCore(data) {
530
- let isErr = false
531
-
532
- //check
533
- if (!iseobj(data) &amp;&amp; !isearr(data)) {
534
- return []
535
- }
536
-
537
- //cloneDeep, 與外部數據脫勾
538
- data = cloneDeep(data)
539
-
540
- //res
541
- let res = null
542
- try {
543
-
544
- //check
545
- if (!isarr(data)) {
546
- data = [data]
547
- }
548
-
549
- //read
550
- await lowdb.read()
551
-
552
- //default, 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
553
- if (!haskey(lowdb.data, key)) {
554
- lowdb.data[key] = []
555
- }
556
-
557
- //kp
558
- let kp = {}
559
- each(lowdb.data[key], (v, k) => {
560
- kp[v.id] = { k, v }
561
- })
562
-
563
- //pmSeries
564
- let ks = []
565
- let b = false
566
- res = await pmSeries(data, async(v) => {
567
-
568
- //rest
569
- let rest = null
570
-
571
- //id
572
- let id = get(v, 'id', '')
573
-
574
- //check
575
- if (isestr(id)) {
576
-
577
- //查找資料表內v.id為_v
578
- let r = get(kp, id, null)
579
-
580
- //check
581
- if (iseobj(r)) {
582
- //已存在v.id則須刪除
583
-
584
- //push
585
- ks.push(r.k)
586
-
587
- b = true
588
-
589
- //rest
590
- rest = {
591
- n: 1,
592
- nDeleted: 1,
593
- ok: 1,
594
- }
595
-
596
- }
597
- else {
598
- //不存在v.id則不刪除
599
-
600
- //rest
601
- rest = {
602
- n: 1,
603
- nDeleted: 0,
604
- ok: 1,
605
- }
606
-
607
- }
608
-
609
- }
610
- else {
611
- //未給v.id則不刪除
612
-
613
- //rest
614
- rest = {
615
- n: 1,
616
- nDeleted: 0,
617
- ok: 0, //未給v.id視為有問題數據, 故ok給0
618
- }
619
-
620
- }
621
-
622
- return rest
623
- })
624
-
625
- //update
626
- if (b) {
627
- lowdb.data[key] = arrPullAt(lowdb.data[key], ks)
628
- }
629
-
630
- //write
631
- if (b) {
632
- await lowdb.write()
633
- }
634
-
635
- }
636
- catch (err) {
637
- isErr = true
638
- res = err
639
- }
640
-
641
- //update, 不能保證刪除多少, 一律重設快取
642
- _cache = null
643
-
644
- //emit, 於change可能須使用select, 故須放在重設快取之後
645
- if (!isErr) {
646
- try {
647
-
648
- //emit
649
- ee.emit('change', 'del', data, res)
650
-
651
- }
652
- catch (err) {
653
- console.log(err)
654
- }
655
- }
656
-
657
- if (isErr) {
658
- return Promise.reject(res)
659
- }
660
- return res
661
- }
662
- async function del(data) {
663
- if (kpGlock[gkey]) {
664
- await waitFun(() => {
665
- return !kpGlock[gkey]
666
- })
667
- }
668
- kpGlock[gkey] = true
669
- return await delCore(data)
670
- .finally(() => {
671
- kpGlock[gkey] = false
672
- })
673
- }
674
-
675
- /**
676
- * 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
677
- *
678
- * @memberOf WOrmLowdb
679
- * @param {Object} [find={}] 輸入刪除條件物件
680
- * @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
681
- */
682
- async function delAllCore(find = {}) {
683
- let isErr = false
684
-
685
- //res
686
- let res = null
687
- try {
688
-
689
- //read
690
- await lowdb.read()
691
-
692
- //default, 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
693
- if (!haskey(lowdb.data, key)) {
694
- lowdb.data[key] = []
695
- }
696
-
697
- //filter
698
- let nAll = size(lowdb.data[key])
699
- let nDel = 0
700
- let b = false
701
- if (iseobj(find)) {
702
-
703
- //q
704
- let q = new Query(find)
705
- // console.log('q', q)
706
-
707
- //find
708
- let _res = q.find(lowdb.data[key]).all()
709
- // console.log('_res', _res)
710
-
711
- //nDel
712
- nDel = size(_res)
713
- // console.log('nDel', nDel)
714
-
715
- if (nDel === 0) {
716
- //未有find結果等於不刪除
717
- }
718
- else if (nAll === nDel) {
719
- //全在find結果內等於全部刪除
720
-
721
- //empty
722
- lowdb.data[key] = []
723
-
724
- b = true
725
- }
726
- else {
727
- //部份在find結果內
728
-
729
- //_kp
730
- let _kp = {}
731
- each(_res, (v, k) => {
732
- _kp[v.id] = { k, v }
733
- })
734
-
735
- //arr
736
- let arr = []
737
- each(lowdb.data[key], (v, k) => {
738
- if (!haskey(_kp, v.id)) {
739
- //未在find結果內代表須保留
740
- arr.push(v)
741
- }
742
- })
743
-
744
- //update
745
- lowdb.data[key] = arr
746
-
747
- b = true
748
- }
749
- }
750
- else {
751
-
752
- //nDel
753
- nDel = nAll
754
-
755
- //empty
756
- lowdb.data[key] = []
757
-
758
- b = true
759
- }
760
-
761
- //write
762
- if (b) {
763
- await lowdb.write()
764
- }
765
-
766
- //res
767
- res = {
768
- n: nAll,
769
- nDeleted: nDel,
770
- ok: 1,
771
- }
772
-
773
- }
774
- catch (err) {
775
- isErr = true
776
- res = err
777
- }
778
-
779
- //update, 不能保證刪除多少, 一律重設快取
780
- _cache = null
781
-
782
- //emit, 於change可能須使用select, 故須放在重設快取之後
783
- if (!isErr) {
784
- try {
785
-
786
- //emit
787
- ee.emit('change', 'delAll', null, res)
788
-
789
- }
790
- catch (err) {
791
- console.log(err)
792
- }
793
- }
794
-
795
- if (isErr) {
796
- return Promise.reject(res)
797
- }
798
- return res
799
- }
800
- async function delAll(find = {}) {
801
- if (kpGlock[gkey]) {
802
- await waitFun(() => {
803
- return !kpGlock[gkey]
804
- })
805
- }
806
- kpGlock[gkey] = true
807
- return await delAllCore(find)
808
- .finally(() => {
809
- kpGlock[gkey] = false
810
- })
811
- }
812
-
813
- //bind
814
- ee.select = select
815
- ee.insert = insert
816
- ee.save = save
817
- ee.del = del
818
- ee.delAll = delAll
819
-
820
- return ee
821
- }
822
-
823
-
824
- export default WOrmLowdb
48
+ <pre class="prettyprint source linenums"><code>import path from 'path'
49
+ import { Low } from 'lowdb'
50
+ import { JSONFile } from 'lowdb/node'
51
+ import { Query } from 'mingo'
52
+ import get from 'lodash-es/get.js'
53
+ import each from 'lodash-es/each.js'
54
+ import map from 'lodash-es/map.js'
55
+ import filter from 'lodash-es/filter.js'
56
+ import merge from 'lodash-es/merge.js'
57
+ import isEqual from 'lodash-es/isEqual.js'
58
+ import size from 'lodash-es/size.js'
59
+ import cloneDeep from 'lodash-es/cloneDeep.js'
60
+ import evem from 'wsemi/src/evem.mjs'
61
+ import genIDSeq from 'wsemi/src/genIDSeq.mjs'
62
+ import pmSeries from 'wsemi/src/pmSeries.mjs'
63
+ import isarr from 'wsemi/src/isarr.mjs'
64
+ import isbol from 'wsemi/src/isbol.mjs'
65
+ import isestr from 'wsemi/src/isestr.mjs'
66
+ import iseobj from 'wsemi/src/iseobj.mjs'
67
+ import isearr from 'wsemi/src/isearr.mjs'
68
+ import haskey from 'wsemi/src/haskey.mjs'
69
+
70
+
71
+ //kpGqueue, 以資料庫檔案路徑為鍵之全域序列化佇列, 詳見下方serialize說明
72
+ let kpGqueue = {}
73
+
74
+
75
+ /**
76
+ * 操作資料庫(lowdb)
77
+ *
78
+ * 回傳物件為EventEmitter,除各操作函數外另發出change與error兩事件,供呼叫端於單一處集中觀察資料異動與失敗。
79
+ * 事件僅為附加通知,其所送出之資訊皆另有正規管道(操作結果經resolve、整批性錯誤經reject、逐筆失敗經該筆之err欄位),
80
+ * 故不監聽亦能取得完整資訊,且監聽與否不改變任何操作之回傳值。
81
+ *
82
+ * change事件,參數為(mode, data, res),於資料實際異動成功後發出:
83
+ * mode為操作別字串,可為'insert'、'insertBulk'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
84
+ * data為本次操作之輸入數據,delAll固定為null。
85
+ * res為本次操作之回傳結果。
86
+ * 逐筆函數以整批為單位發出一次而不逐筆發出,select與selectByPk不發出本事件。
87
+ *
88
+ * error事件,參數為(mode, data, err),於操作發生錯誤時發出:
89
+ * mode為操作別字串,可為'select'、'selectByPk'、'insert'、'insertBulk'、'save'、'del'、'delAll'。
90
+ * data為本次操作之輸入數據,無輸入數據者為null。
91
+ * err為錯誤訊息字串,內容與正規管道所送出者一致。
92
+ * 整批性錯誤於reject之前發出;逐筆失敗於該筆結果定案後發出,每筆一次。
93
+ * 註: 逐筆失敗時整批仍resolve,故收到本事件不表示該次呼叫失敗;正常結果(如查無數據、主鍵未命中、全數已存在)不發出本事件。
94
+ *
95
+ * 註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵。
96
+ * 註: 本套件之寫入為[整檔讀出、記憶體修改、整檔寫回],其原子性由行程內之序列化佇列提供,
97
+ * 故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。
98
+ *
99
+ * @class
100
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
101
+ * @param {String} [opt.url='./db.json'] 輸入資料庫位置字串,預設'./db.json'
102
+ * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
103
+ * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
104
+ * @param {Boolean} [opt.useCache=false] 輸入是否使用select快取,適用於單程序操作,預設false
105
+ * @param {Boolean} [opt.autoGenPk=true] 輸入insert、insertBulk與save於數據未帶有效主鍵(本套件為id欄位)時是否自動產生主鍵值,預設true;給false代表主鍵改由呼叫端自備,套件不產生亦不檢查其唯一性與格式,未帶有效主鍵者將reject
106
+ * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
107
+ */
108
+ function WOrmLowdb(opt = {}) {
109
+
110
+ //_cache
111
+ let _cache = null
112
+
113
+ //url
114
+ let url = get(opt, 'url')
115
+ if (!isestr(url)) {
116
+ url = './db.json'
117
+ }
118
+
119
+ //db
120
+ let db = get(opt, 'db')
121
+ if (!isestr(db)) {
122
+ db = 'worm'
123
+ }
124
+
125
+ //cl
126
+ let cl = get(opt, 'cl')
127
+ if (!isestr(cl)) {
128
+ cl = 'test'
129
+ }
130
+
131
+ //useCache
132
+ let useCache = get(opt, 'useCache')
133
+ if (!isbol(useCache)) {
134
+ useCache = false
135
+ }
136
+
137
+ //autoGenPk, 主鍵由誰產生為整個資料表之政策, 故為建構層設定, 不可於各寫入函數之option逐次覆寫,
138
+ //否則同一資料表將混入兩種來源之主鍵而難以追溯
139
+ let autoGenPk = get(opt, 'autoGenPk')
140
+ if (!isbol(autoGenPk)) {
141
+ autoGenPk = true
142
+ }
143
+
144
+ //key
145
+ let key = `${db}:${cl}`
146
+
147
+ //adapter
148
+ let adapter = new JSONFile(url)
149
+
150
+ //lowdb
151
+ let lowdb = new Low(adapter, {})
152
+
153
+ //ee
154
+ let ee = evem() //採eventemitter3, 其於'error'無監聽者時僅回傳false而不拋出, 故操作行為不因監聽者之有無而改變
155
+
156
+ //gkey, 基於url也就是以檔案路徑為準, 同檔案路徑須使用同一佇列
157
+ let gkey = path.resolve(url)
158
+
159
+ //佇列初始化, 同一資料庫檔案之全部操作一律序列化
160
+ if (!haskey(kpGqueue, gkey)) {
161
+ kpGqueue[gkey] = Promise.resolve()
162
+ }
163
+
164
+ /**
165
+ * 序列化同一資料庫檔案之操作
166
+ *
167
+ * 本套件之寫入為[整檔讀出、記憶體修改、整檔寫回],lowdb無條件寫入亦無比較並交換,
168
+ * 故insert之[檢查主鍵不存在與寫入]與save之[查找主鍵與更新或插入],其原子性只能由行程內之序列化達成。
169
+ * 採Promise鏈而非[布林旗標配合輪詢等待],係因後者之[檢查旗標]與[設定旗標]之間隔有await,
170
+ * 並非原子之test-and-set,多個等候者之輪詢若落在同一批timer觸發即會同時進入臨界區;
171
+ * 且輪詢函數逾時後為resolve而非reject,等滿即照樣放行。Promise鏈為嚴格FIFO且無逾時放行,由結構保證互斥。
172
+ * select亦納入序列化,令其不致與寫入之整檔替換交錯。
173
+ *
174
+ * 註: 佇列為module層之變數,僅同一行程內共享,故跨行程併發不受此保護。
175
+ */
176
+ let serialize = (fn) => {
177
+
178
+ //pmPrev, 恆為已攔截錯誤之Promise, 故前一次操作失敗不會中斷佇列
179
+ let pmPrev = kpGqueue[gkey]
180
+
181
+ //pm
182
+ let pm = pmPrev.then(() => {
183
+ return fn()
184
+ })
185
+
186
+ //update, 存入已攔截錯誤者, 避免本次reject成為unhandled
187
+ kpGqueue[gkey] = pm.catch(() => {})
188
+
189
+ return pm
190
+ }
191
+
192
+ //emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
193
+ //一律包try/catch, 令訂閱函數自身拋錯不影響本次操作之結果
194
+ let emitChange = (mode, data, res) => {
195
+ try {
196
+ ee.emit('change', mode, data, res)
197
+ }
198
+ catch (err) {
199
+ console.log(err)
200
+ }
201
+ }
202
+
203
+ //emitError, 操作發生錯誤時發出, 錯誤訊息一律轉為字串
204
+ let emitError = (mode, data, err) => {
205
+ try {
206
+ ee.emit('error', mode, data, getErrMsg(err))
207
+ }
208
+ catch (errEmit) {
209
+ console.log(errEmit)
210
+ }
211
+ }
212
+
213
+ //getErrMsg, 取錯誤訊息字串, 供逐筆結果之err欄位與error事件使用
214
+ let getErrMsg = (err) => {
215
+ let m = get(err, 'message')
216
+ if (isestr(m)) {
217
+ return m
218
+ }
219
+ return String(err)
220
+ }
221
+
222
+ //procPk, 依autoGenPk處理各數據之主鍵, 供insert、insertBulk與save共用
223
+ //autoGenPk為true時補值; 為false時不補值, 未帶有效主鍵者屬呼叫端未履行契約, 拋出為整批性錯誤,
224
+ //且檢查於任何寫入之前完成, 故不會有部份筆數已寫入而整批失敗之情形
225
+ let procPk = (data, fnName) => {
226
+ if (autoGenPk) {
227
+ return map(data, function(v) {
228
+ if (!isestr(v.id)) {
229
+ v.id = genIDSeq()
230
+ }
231
+ return v
232
+ })
233
+ }
234
+ each(data, function(v, k) {
235
+ if (!isestr(get(v, 'id'))) {
236
+ throw new Error(`can not ${fnName} by data[${k}] without valid id when autoGenPk is false`)
237
+ }
238
+ })
239
+ return data
240
+ }
241
+
242
+ //readData, 讀取資料庫檔案並取得本資料表之陣列
243
+ //註: 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
244
+ let readData = async() => {
245
+ await lowdb.read()
246
+ if (!haskey(lowdb.data, key)) {
247
+ lowdb.data[key] = []
248
+ }
249
+ return lowdb.data[key]
250
+ }
251
+
252
+ //writeData, 以新陣列取代本資料表後寫檔
253
+ //寫檔失敗時還原記憶體狀態, 令reject之後之記憶體與檔案一致,
254
+ //否則殘留之修改會於下次寫入時被一併帶出, 使本應未生效之數據落盤
255
+ let writeData = async(arrOld, arrNew) => {
256
+ lowdb.data[key] = arrNew
257
+ try {
258
+ await lowdb.write()
259
+ }
260
+ catch (err) {
261
+ lowdb.data[key] = arrOld
262
+ throw err
263
+ }
264
+ }
265
+
266
+ //getKpPk, 由數據陣列建立[主鍵值 => {k, v}]之對照表, k為所在索引
267
+ //僅收錄具有效主鍵值者, 令資料表內若有主鍵值無效之殘留數據不致被誤配為命中
268
+ let getKpPk = (ltdt) => {
269
+ let kp = {}
270
+ each(ltdt, (v, k) => {
271
+ let id = get(v, 'id')
272
+ if (isestr(id)) {
273
+ kp[id] = { k, v }
274
+ }
275
+ })
276
+ return kp
277
+ }
278
+
279
+ //getData
280
+ let getData = async() => {
281
+
282
+ //check
283
+ if (useCache &amp;&amp; isarr(_cache)) {
284
+ return cloneDeep(_cache) //與外部使用數據脫勾
285
+ }
286
+
287
+ //read
288
+ let ltdt = await readData()
289
+
290
+ //cloneDeep, 數據為lowdb內部的記憶體狀態本體, 須cloneDeep與外部使用數據脫勾
291
+ ltdt = cloneDeep(ltdt)
292
+
293
+ if (useCache) {
294
+ _cache = ltdt
295
+ return cloneDeep(ltdt) //與外部使用數據脫勾
296
+ }
297
+ return ltdt
298
+ }
299
+
300
+ /**
301
+ * 查詢數據
302
+ *
303
+ * @memberOf WOrmLowdb
304
+ * @param {Object} [find={}] 輸入查詢條件物件
305
+ * @returns {Promise} 回傳Promise,resolve回傳數據陣列,無符合數據回傳空陣列,reject回傳錯誤訊息
306
+ */
307
+ async function selectCore(find = {}) {
308
+ let isErr = false
309
+
310
+ //res
311
+ let res = null
312
+ try {
313
+
314
+ //ltdt
315
+ let ltdt = await getData()
316
+
317
+ //filter
318
+ if (iseobj(find)) {
319
+
320
+ //q
321
+ let q = new Query(find)
322
+ // console.log('q', q)
323
+
324
+ //find
325
+ res = q.find(ltdt).all()
326
+ // console.log('res', res)
327
+
328
+ }
329
+ else {
330
+ res = ltdt
331
+ }
332
+
333
+ //check
334
+ if (!isarr(res)) {
335
+ isErr = true
336
+ res = new Error(`can not select by find[${JSON.stringify(find)}]`)
337
+ }
338
+
339
+ }
340
+ catch (err) {
341
+ isErr = true
342
+ res = err
343
+ }
344
+
345
+ //check
346
+ if (isErr) {
347
+
348
+ //emit, 整批性錯誤須於reject之前發出
349
+ emitError('select', null, res)
350
+
351
+ return Promise.reject(res)
352
+ }
353
+
354
+ return res
355
+ }
356
+ async function select(find = {}) {
357
+ return serialize(() => selectCore(find))
358
+ }
359
+
360
+ /**
361
+ * 由主鍵查詢單筆數據
362
+ * 註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵
363
+ * 註: lowdb無索引,本函數仍須讀取全表,故較select僅省去查詢條件之過濾而無數量級之效能差異,
364
+ * 提供本函數係為與其他w-orm系列套件維持同一組函數
365
+ *
366
+ * @memberOf WOrmLowdb
367
+ * @param {String} pk 輸入主鍵值字串,本套件之主鍵欄位為id
368
+ * @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵或主鍵值無效則回傳null,reject回傳錯誤訊息
369
+ */
370
+ async function selectByPkCore(pk) {
371
+ let isErr = false
372
+
373
+ //res
374
+ let res = null
375
+ try {
376
+
377
+ //check
378
+ if (!isestr(pk)) {
379
+ //未給有效主鍵值視為查無數據
380
+ return null
381
+ }
382
+
383
+ //ltdt
384
+ let ltdt = await getData()
385
+
386
+ //查找資料表內pk, 判定基準與insert、insertBulk、save、del內對既有數據之認定一致(皆以id之字串值比對)
387
+ let kp = getKpPk(ltdt)
388
+ let r = get(kp, pk, null)
389
+
390
+ //check
391
+ if (iseobj(r)) {
392
+ res = r.v
393
+ }
394
+ else {
395
+ //不存在此主鍵, 回傳null
396
+ res = null
397
+ }
398
+
399
+ }
400
+ catch (err) {
401
+ isErr = true
402
+ res = err
403
+ }
404
+
405
+ //check
406
+ if (isErr) {
407
+
408
+ //emit, 整批性錯誤須於reject之前發出
409
+ emitError('selectByPk', null, res)
410
+
411
+ return Promise.reject(res)
412
+ }
413
+
414
+ return res
415
+ }
416
+ async function selectByPk(pk) {
417
+ return serialize(() => selectByPkCore(pk))
418
+ }
419
+
420
+ /**
421
+ * 插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫
422
+ *
423
+ * @memberOf WOrmLowdb
424
+ * @param {Object|Array} data 輸入數據物件或陣列
425
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
426
+ * @param {Boolean} [option.returnList=false] 輸入是否改回傳逐筆結果布林值,預設false。給true時回傳與輸入等長保序之陣列,各筆為{n,nInserted,ok},nInserted為1即該筆為新增;聚合計數只回答有幾筆是新的,逐筆結果方能回答是哪幾筆,供下游僅對新資料執行昂貴動作。回傳形式由呼叫點靜態決定,共用之結果處理程式碼不可跨不同取值之呼叫點混用
427
+ * @returns {Promise} 回傳Promise,resolve依returnList回傳插入結果:預設回傳聚合物件{n,nInserted,ok},n為輸入筆數、nInserted為實際插入筆數;returnList為true時回傳逐筆陣列,各筆n恆為1(主鍵命中或經插入)、ok恆為1(insert之錯誤皆屬整批性而reject,不進逐筆);reject回傳錯誤訊息
428
+ */
429
+ async function insertCore(data, option = {}) {
430
+ let isErr = false
431
+
432
+ //returnList
433
+ let returnList = get(option, 'returnList')
434
+ if (!isbol(returnList)) {
435
+ returnList = false
436
+ }
437
+
438
+ //check
439
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
440
+ if (returnList) {
441
+ //輸入無效, 對齊save與del之空陣列規定
442
+ return []
443
+ }
444
+ return {
445
+ n: 0,
446
+ nInserted: 0,
447
+ ok: 1,
448
+ }
449
+ }
450
+
451
+ //cloneDeep, 與外部數據脫勾
452
+ data = cloneDeep(data)
453
+
454
+ //res
455
+ let res = null
456
+ try {
457
+
458
+ //check
459
+ if (!isarr(data)) {
460
+ data = [data]
461
+ }
462
+
463
+ //check id
464
+ data = procPk(data, 'insert')
465
+
466
+ //read
467
+ let arrOld = await readData()
468
+
469
+ //kp
470
+ let kp = getKpPk(arrOld)
471
+
472
+ //each
473
+ let nAll = size(data)
474
+ let ltdone = []
475
+ let ltpush = []
476
+ each(data, (v) => {
477
+
478
+ //check
479
+ if (!haskey(kp, v.id)) {
480
+ //未存在v.id
481
+
482
+ //push
483
+ ltpush.push(v)
484
+
485
+ //update, 須同步更新kp, 否則同批含重複主鍵者會全數寫入而出現重複主鍵
486
+ kp[v.id] = { k: -1, v }
487
+
488
+ ltdone.push(true)
489
+ }
490
+ else {
491
+ //已存在v.id(含同批重複之非首筆)則不push
492
+ ltdone.push(false)
493
+ }
494
+
495
+ })
496
+
497
+ //write, 未有須插入者即不寫檔, 全數已存在屬正常結果
498
+ if (size(ltpush) > 0) {
499
+ await writeData(arrOld, [...arrOld, ...ltpush])
500
+ }
501
+
502
+ //res
503
+ if (returnList) {
504
+ //逐筆結果, 與輸入等長保序, 元素沿用逐筆結果之家族形狀(同save與del之元素)
505
+ //n恆為1(主鍵命中既有或經插入而產生), ok恆為1(insert之錯誤皆屬整批性而reject, 不進逐筆),
506
+ //資訊由nInserted承載, filter(v=>v.nInserted===1)之長度必等於聚合模式之nInserted
507
+ res = map(ltdone, function(done) {
508
+ return {
509
+ n: 1,
510
+ nInserted: done ? 1 : 0,
511
+ ok: 1,
512
+ }
513
+ })
514
+ }
515
+ else {
516
+ res = {
517
+ n: nAll,
518
+ nInserted: size(ltpush),
519
+ ok: 1,
520
+ }
521
+ }
522
+
523
+ }
524
+ catch (err) {
525
+ isErr = true
526
+ res = err
527
+ }
528
+
529
+ //update, 不能保證插入多少, 一律重設快取
530
+ _cache = null
531
+
532
+ //emit, 於change可能須使用select, 故須放在重設快取之後
533
+ if (!isErr) {
534
+ emitChange('insert', data, res)
535
+ }
536
+
537
+ //check
538
+ if (isErr) {
539
+
540
+ //emit, 整批性錯誤須於reject之前發出
541
+ emitError('insert', data, res)
542
+
543
+ return Promise.reject(res)
544
+ }
545
+
546
+ return res
547
+ }
548
+ async function insert(data, option = {}) {
549
+ return serialize(() => insertCore(data, option))
550
+ }
551
+
552
+ /**
553
+ * 批次插入數據,全批視為一個單位,全部插入成功或一筆都不寫入
554
+ * 註: 本函數非insert之加速版,兩者衝突政策不同。insert於主鍵已存在時跳過該筆而整批ok為1,
555
+ * 本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
556
+ * 註: 於本套件不會較insert快,因兩者皆為一次讀檔與一次寫檔,
557
+ * 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
558
+ *
559
+ * @memberOf WOrmLowdb
560
+ * @param {Object|Array} data 輸入數據物件或陣列
561
+ * @returns {Promise} 回傳Promise,resolve回傳插入結果物件{n,nInserted,ok},n為輸入筆數、nInserted成功時恆等於n,任一筆主鍵已存在則reject回傳錯誤訊息
562
+ */
563
+ async function insertBulkCore(data) {
564
+ let isErr = false
565
+
566
+ //check
567
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
568
+ return {
569
+ n: 0,
570
+ nInserted: 0,
571
+ ok: 1,
572
+ }
573
+ }
574
+
575
+ //cloneDeep, 與外部數據脫勾
576
+ data = cloneDeep(data)
577
+
578
+ //res
579
+ let res = null
580
+ try {
581
+
582
+ //check
583
+ if (!isarr(data)) {
584
+ data = [data]
585
+ }
586
+
587
+ //check id
588
+ data = procPk(data, 'insertBulk')
589
+
590
+ //read
591
+ let arrOld = await readData()
592
+
593
+ //kp
594
+ let kp = getKpPk(arrOld)
595
+
596
+ //nAll
597
+ let nAll = size(data)
598
+
599
+ //check, 主鍵檢查於任何寫入之前一次完成, 任一筆衝突即整批中止,
600
+ //同批含重複主鍵者亦於此被偵測為衝突(kp隨檢查同步更新)
601
+ let pkConflict = null
602
+ each(data, (v) => {
603
+ if (haskey(kp, v.id)) {
604
+ pkConflict = v.id
605
+ return false //中止each
606
+ }
607
+ kp[v.id] = { k: -1, v }
608
+ })
609
+
610
+ //check
611
+ if (pkConflict !== null) {
612
+ throw new Error(`can not insertBulk by existed id[${pkConflict}]`)
613
+ }
614
+
615
+ //write, 全部通過檢查方寫入, 且寫檔為單次[寫暫存檔後更名]之原子替換,
616
+ //故全有全無成立: 檢查失敗時尚未修改任何狀態, 寫檔失敗時writeData會還原記憶體
617
+ await writeData(arrOld, [...arrOld, ...data])
618
+
619
+ //res, 未衝突則全數插入, 故nInserted恆等於n
620
+ res = {
621
+ n: nAll,
622
+ nInserted: nAll,
623
+ ok: 1,
624
+ }
625
+
626
+ }
627
+ catch (err) {
628
+ isErr = true
629
+ res = err
630
+ }
631
+
632
+ //update, 不能保證插入多少, 一律重設快取
633
+ _cache = null
634
+
635
+ //emit, 於change可能須使用select, 故須放在重設快取之後
636
+ if (!isErr) {
637
+ emitChange('insertBulk', data, res)
638
+ }
639
+
640
+ //check
641
+ if (isErr) {
642
+
643
+ //emit, 整批性錯誤須於reject之前發出
644
+ emitError('insertBulk', data, res)
645
+
646
+ return Promise.reject(res)
647
+ }
648
+
649
+ return res
650
+ }
651
+ async function insertBulk(data) {
652
+ return serialize(() => insertBulkCore(data))
653
+ }
654
+
655
+ /**
656
+ * 儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且autoInsert為true時改為插入
657
+ *
658
+ * @memberOf WOrmLowdb
659
+ * @param {Object|Array} data 輸入數據物件或陣列
660
+ * @param {Object} [option={}] 輸入設定物件,預設為{}
661
+ * @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
662
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之儲存結果陣列,各筆為{n,nInserted,nModified,ok},n為主鍵命中筆數,單筆失敗者ok為0並附err,reject回傳錯誤訊息
663
+ */
664
+ async function saveCore(data, option = {}) {
665
+ let isErr = false
666
+
667
+ //check
668
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
669
+ return []
670
+ }
671
+
672
+ //cloneDeep, 與外部數據脫勾
673
+ data = cloneDeep(data)
674
+
675
+ //autoInsert
676
+ let autoInsert = get(option, 'autoInsert', true)
677
+
678
+ //res
679
+ let res = null
680
+ try {
681
+
682
+ //check
683
+ if (!isarr(data)) {
684
+ data = [data]
685
+ }
686
+
687
+ //check id
688
+ data = procPk(data, 'save')
689
+
690
+ //read
691
+ let arrOld = await readData()
692
+
693
+ //arrNew, 逐筆修改於複本上進行, 全部完成後方取代與寫檔
694
+ let arrNew = [...arrOld]
695
+
696
+ //kp
697
+ let kp = getKpPk(arrNew)
698
+
699
+ //pmSeries
700
+ let b = false
701
+ res = await pmSeries(data, async(v) => {
702
+
703
+ //rest
704
+ let rest = null
705
+
706
+ try {
707
+
708
+ //查找資料表內v.id
709
+ let r = get(kp, v.id, null)
710
+
711
+ //existed
712
+ let existed = iseobj(r)
713
+
714
+ //check
715
+ if (existed) {
716
+ //已存在v.id
717
+
718
+ //vv, 現值
719
+ let vv = r.v
720
+
721
+ //vm, 合併後之結果, 另建新物件以免污染原數據
722
+ let vm = merge({}, vv, v)
723
+
724
+ //判定基準為[合併後結果與現值相同], 而非[待寫入物件與現值全等],
725
+ //令nModified忠實反映是否真的寫入, 僅給部份欄位且值皆相同者不應回報已修改
726
+ if (isEqual(vm, vv)) {
727
+ //合併後與現值相同不須寫入, 惟已命中v.id故n為1
728
+ rest = {
729
+ n: 1,
730
+ nInserted: 0,
731
+ nModified: 0,
732
+ ok: 1,
733
+ }
734
+ }
735
+ else {
736
+ //合併後與現值不同須更新
737
+
738
+ //update
739
+ arrNew[r.k] = vm
740
+ kp[v.id] = { k: r.k, v: vm }
741
+
742
+ b = true
743
+
744
+ rest = {
745
+ n: 1,
746
+ nInserted: 0,
747
+ nModified: 1,
748
+ ok: 1,
749
+ }
750
+ }
751
+
752
+ }
753
+ else {
754
+ //不存在v.id, 若autoInsert則須插入
755
+ if (autoInsert) {
756
+
757
+ //push
758
+ let k = size(arrNew)
759
+ arrNew.push(v)
760
+ kp[v.id] = { k, v }
761
+
762
+ b = true
763
+
764
+ rest = {
765
+ n: 1,
766
+ nInserted: 1,
767
+ nModified: 0,
768
+ ok: 1,
769
+ }
770
+
771
+ }
772
+ else {
773
+ //未命中且未開啟autoInsert, 未命中故n為0
774
+ rest = {
775
+ n: 0,
776
+ nInserted: 0,
777
+ nModified: 0,
778
+ ok: 1,
779
+ }
780
+ }
781
+ }
782
+
783
+ }
784
+ catch (err) {
785
+
786
+ //本筆失敗不中斷整批, 以ok為0並附err回報, 由呼叫端逐筆檢查
787
+ rest = {
788
+ n: 1,
789
+ nInserted: 0,
790
+ nModified: 0,
791
+ ok: 0,
792
+ err: getErrMsg(err),
793
+ }
794
+
795
+ }
796
+
797
+ return rest
798
+ })
799
+
800
+ //write
801
+ if (b) {
802
+ await writeData(arrOld, arrNew)
803
+ }
804
+
805
+ }
806
+ catch (err) {
807
+ isErr = true
808
+ res = err
809
+ }
810
+
811
+ //update, 不能保證變更多少, 一律重設快取
812
+ _cache = null
813
+
814
+ //emit, 於change可能須使用select, 故須放在重設快取之後
815
+ if (!isErr) {
816
+
817
+ //emit, 逐筆失敗於該筆結果定案後發出, 每筆一次
818
+ //此事件之發出不表示整批失敗, 整批仍resolve, 該筆以ok為0回報
819
+ each(res, (rest, k) => {
820
+ if (get(rest, 'ok') === 0) {
821
+ emitError('save', [get(data, k)], get(rest, 'err'))
822
+ }
823
+ })
824
+
825
+ //emit, 逐筆插入另發出mode為insert之事件, 供呼叫端區辨新增與更新
826
+ //逐筆結果與輸入等長保序, 故由nInserted即可定位係哪幾筆走插入, 不須另行記錄
827
+ each(res, (rest, k) => {
828
+ if (get(rest, 'nInserted') === 1) {
829
+ emitChange('insert', [get(data, k)], rest)
830
+ }
831
+ })
832
+
833
+ //emit, 逐筆失敗之error事件已於上方發出, 故必早於本整批change
834
+ emitChange('save', data, res)
835
+
836
+ }
837
+
838
+ //check
839
+ if (isErr) {
840
+
841
+ //emit, 整批性錯誤須於reject之前發出
842
+ emitError('save', data, res)
843
+
844
+ return Promise.reject(res)
845
+ }
846
+
847
+ return res
848
+ }
849
+ async function save(data, option = {}) {
850
+ return serialize(() => saveCore(data, option))
851
+ }
852
+
853
+ /**
854
+ * 刪除數據,依主鍵刪除
855
+ *
856
+ * @memberOf WOrmLowdb
857
+ * @param {Object|Array} data 輸入數據物件或陣列
858
+ * @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列,各筆為{n,nDeleted,ok},n為主鍵命中筆數,單筆失敗或未給有效id者ok為0並附err,reject回傳錯誤訊息
859
+ */
860
+ async function delCore(data) {
861
+ let isErr = false
862
+
863
+ //check
864
+ if (!iseobj(data) &amp;&amp; !isearr(data)) {
865
+ return []
866
+ }
867
+
868
+ //cloneDeep, 與外部數據脫勾
869
+ data = cloneDeep(data)
870
+
871
+ //res
872
+ let res = null
873
+ try {
874
+
875
+ //check
876
+ if (!isarr(data)) {
877
+ data = [data]
878
+ }
879
+
880
+ //read
881
+ let arrOld = await readData()
882
+
883
+ //kp
884
+ let kp = getKpPk(arrOld)
885
+
886
+ //pmSeries
887
+ let kpDel = {}
888
+ let b = false
889
+ res = await pmSeries(data, async(v) => {
890
+
891
+ //rest
892
+ let rest = null
893
+
894
+ try {
895
+
896
+ //id
897
+ let id = get(v, 'id', '')
898
+
899
+ //check
900
+ if (!isestr(id)) {
901
+ //未給有效v.id視為該筆數據有問題而無法處理, 以ok為0並附err回報,
902
+ //與[已給v.id但查無數據]之ok為1有別, 二者須可由ok分辨
903
+ //註: 亦不可將無效主鍵送進查詢條件, 故於此直接定案
904
+ rest = {
905
+ n: 0,
906
+ nDeleted: 0,
907
+ ok: 0,
908
+ err: `can not delete by invalid id[${id}]`,
909
+ }
910
+ }
911
+ else {
912
+
913
+ //查找資料表內v.id
914
+ let r = get(kp, id, null)
915
+
916
+ //check
917
+ if (iseobj(r)) {
918
+ //已存在v.id則須刪除
919
+
920
+ //push
921
+ kpDel[r.k] = true
922
+
923
+ //update, 須同步移除, 否則同批重複主鍵者會重複計入nDeleted
924
+ delete kp[id]
925
+
926
+ b = true
927
+
928
+ rest = {
929
+ n: 1,
930
+ nDeleted: 1,
931
+ ok: 1,
932
+ }
933
+
934
+ }
935
+ else {
936
+ //不存在v.id則不刪除, 未命中故n為0
937
+ rest = {
938
+ n: 0,
939
+ nDeleted: 0,
940
+ ok: 1,
941
+ }
942
+
943
+ }
944
+
945
+ }
946
+
947
+ }
948
+ catch (err) {
949
+
950
+ //本筆失敗不中斷整批, 以ok為0並附err回報, 由呼叫端逐筆檢查
951
+ rest = {
952
+ n: 1,
953
+ nDeleted: 0,
954
+ ok: 0,
955
+ err: getErrMsg(err),
956
+ }
957
+
958
+ }
959
+
960
+ return rest
961
+ })
962
+
963
+ //write
964
+ if (b) {
965
+ let arrNew = filter(arrOld, (v, k) => {
966
+ return !haskey(kpDel, k)
967
+ })
968
+ await writeData(arrOld, arrNew)
969
+ }
970
+
971
+ }
972
+ catch (err) {
973
+ isErr = true
974
+ res = err
975
+ }
976
+
977
+ //update, 不能保證刪除多少, 一律重設快取
978
+ _cache = null
979
+
980
+ //emit, 於change可能須使用select, 故須放在重設快取之後
981
+ if (!isErr) {
982
+
983
+ //emit, 逐筆失敗於該筆結果定案後發出, 每筆一次
984
+ //此事件之發出不表示整批失敗, 整批仍resolve, 該筆以ok為0回報
985
+ each(res, (rest, k) => {
986
+ if (get(rest, 'ok') === 0) {
987
+ emitError('del', [get(data, k)], get(rest, 'err'))
988
+ }
989
+ })
990
+
991
+ //emit, 逐筆失敗之error事件已於上方發出, 故必早於本整批change
992
+ emitChange('del', data, res)
993
+
994
+ }
995
+
996
+ //check
997
+ if (isErr) {
998
+
999
+ //emit, 整批性錯誤須於reject之前發出
1000
+ emitError('del', data, res)
1001
+
1002
+ return Promise.reject(res)
1003
+ }
1004
+
1005
+ return res
1006
+ }
1007
+ async function del(data) {
1008
+ return serialize(() => delCore(data))
1009
+ }
1010
+
1011
+ /**
1012
+ * 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
1013
+ *
1014
+ * @memberOf WOrmLowdb
1015
+ * @param {Object} [find={}] 輸入刪除條件物件
1016
+ * @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},n與nDeleted同為實際刪除筆數,reject回傳錯誤訊息
1017
+ */
1018
+ async function delAllCore(find = {}) {
1019
+ let isErr = false
1020
+
1021
+ //res
1022
+ let res = null
1023
+ try {
1024
+
1025
+ //read
1026
+ let arrOld = await readData()
1027
+
1028
+ //filter
1029
+ let nAll = size(arrOld)
1030
+ let nDel = 0
1031
+ let arrNew = null
1032
+ if (iseobj(find)) {
1033
+
1034
+ //q
1035
+ let q = new Query(find)
1036
+ // console.log('q', q)
1037
+
1038
+ //filter, 以與select同一條件逐筆判定, 保留未命中者
1039
+ arrNew = filter(arrOld, (v) => {
1040
+ return !q.test(v)
1041
+ })
1042
+
1043
+ //nDel
1044
+ nDel = nAll - size(arrNew)
1045
+ // console.log('nDel', nDel)
1046
+
1047
+ }
1048
+ else {
1049
+ //find未給或為空物件時刪除全部數據
1050
+
1051
+ //nDel
1052
+ nDel = nAll
1053
+
1054
+ //empty
1055
+ arrNew = []
1056
+
1057
+ }
1058
+
1059
+ //write, 無命中即不寫檔, 屬正常結果
1060
+ if (nDel > 0) {
1061
+ await writeData(arrOld, arrNew)
1062
+ }
1063
+
1064
+ //res, n為實際刪除筆數而非全表筆數, 故與nDeleted同值
1065
+ res = {
1066
+ n: nDel,
1067
+ nDeleted: nDel,
1068
+ ok: 1,
1069
+ }
1070
+
1071
+ }
1072
+ catch (err) {
1073
+ isErr = true
1074
+ res = err
1075
+ }
1076
+
1077
+ //update, 不能保證刪除多少, 一律重設快取
1078
+ _cache = null
1079
+
1080
+ //emit, 於change可能須使用select, 故須放在重設快取之後
1081
+ if (!isErr) {
1082
+ emitChange('delAll', null, res)
1083
+ }
1084
+
1085
+ //check
1086
+ if (isErr) {
1087
+
1088
+ //emit, 整批性錯誤須於reject之前發出
1089
+ emitError('delAll', null, res)
1090
+
1091
+ return Promise.reject(res)
1092
+ }
1093
+
1094
+ return res
1095
+ }
1096
+ async function delAll(find = {}) {
1097
+ return serialize(() => delAllCore(find))
1098
+ }
1099
+
1100
+ //bind
1101
+ ee.select = select
1102
+ ee.selectByPk = selectByPk
1103
+ ee.insert = insert
1104
+ ee.insertBulk = insertBulk
1105
+ ee.save = save
1106
+ ee.del = del
1107
+ ee.delAll = delAll
1108
+
1109
+ return ee
1110
+ }
1111
+
1112
+
1113
+ export default WOrmLowdb
825
1114
  </code></pre>
826
1115
  </article>
827
1116
  </section>
@@ -836,7 +1125,7 @@ export default WOrmLowdb
836
1125
  <br class="clear">
837
1126
 
838
1127
  <footer>
839
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 07 2026 23:37:36 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1128
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 17 2026 20:57:08 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
840
1129
  </footer>
841
1130
 
842
1131
  <script>prettyPrint();</script>