w-orm-lowdb 1.0.36 → 1.0.37

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.
@@ -94,13 +94,24 @@ insert、insertBulk、save與del因輸入本身導致之失敗(如原型為null
94
94
  <p>註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵。
95
95
  註: 本套件之寫入為[整檔讀出、記憶體修改、整檔寫回],其原子性由行程內之序列化佇列提供,
96
96
  故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。
97
- 註: 數據以JSON存檔,Date存入後為ISO字串;insert、insertBulk與save寫入之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject,詳見各函數與README。</p></li></ul></dd>
97
+ 註: 數據以JSON存檔,Date存入後為ISO字串;insert、insertBulk與save寫入之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject,詳見各函數與README。</p>
98
+ <p>字串良構(w-orm系列規則,wsemi之chkOrmStr與chkOrmData): lowdb以JSON檔存放,孤立代理字元(String.prototype.isWellFormed()為false)可保真,本身不致相撞,
99
+ 惟系列其他後端(UTF-8、BSON儲存)會改寫為U+FFFD,為使更換後端時資料行為一致,全系列一律於任何讀取與寫入之前拒收:</p>
100
+ <ol>
101
+ <li>主鍵(id)不得含孤立代理字元與NUL,不隨autoGenPk改變;</li>
102
+ <li>寫入資料(insert、insertBulk、save)之字串值與鍵名(含巢狀物件、陣列元素、Map與Set之項目、Error與RegExp之文字)不得含孤立代理字元,值與鍵名之NUL不拒;</li>
103
+ <li>opt.url、opt.db、opt.cl含孤立代理字元者於建構時throw,訊息為invalid opt.&lt;名&gt;: string contains lone surrogate。
104
+ 處置與無效主鍵同形:insert與insertBulk整批reject而不寫入任何一筆(error事件先於reject),save與del該筆ok為0並附err(同批其他筆照常),selectByPk回null;
105
+ 訊息為固定詞彙而不回顯值:invalid id: 原因 at [data[k].id]、invalid data: 原因 at [data[k].路徑](k為批次索引,單一物件為0;不合格之鍵名以<invalid key>標示),
106
+ 原因為string contains lone surrogate、string contains NUL(僅主鍵)、not a string或string is empty(del之id未給、非字串或空字串)。
107
+ U+FFFD本身與成對代理(emoji等)為良構,照常接受。既有資料不遷移,檢出與清除法見README。</li>
108
+ </ol></li></ul></dd>
98
109
 
99
110
 
100
111
 
101
112
  <dt class="tag-source">Source:</dt>
102
113
  <dd class="tag-source"><ul class="dummy"><li>
103
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line65">line 65</a>
114
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line77">line 77</a>
104
115
  </li></ul></dd>
105
116
 
106
117
 
@@ -272,7 +283,7 @@ insert、insertBulk、save與del因輸入本身導致之失敗(如原型為null
272
283
  </td>
273
284
 
274
285
 
275
- <td class="description last"><p>輸入資料庫位置字串,預設'./db.json'</p></td>
286
+ <td class="description last"><p>輸入資料庫位置字串,預設'./db.json';含孤立代理字元者建構時throw</p></td>
276
287
  </tr>
277
288
 
278
289
 
@@ -312,7 +323,7 @@ insert、insertBulk、save與del因輸入本身導致之失敗(如原型為null
312
323
  </td>
313
324
 
314
325
 
315
- <td class="description last"><p>輸入使用資料庫名稱字串,預設'worm'</p></td>
326
+ <td class="description last"><p>輸入使用資料庫名稱字串,預設'worm';含孤立代理字元者建構時throw</p></td>
316
327
  </tr>
317
328
 
318
329
 
@@ -352,7 +363,7 @@ insert、insertBulk、save與del因輸入本身導致之失敗(如原型為null
352
363
  </td>
353
364
 
354
365
 
355
- <td class="description last"><p>輸入使用資料表名稱字串,預設'test'</p></td>
366
+ <td class="description last"><p>輸入使用資料表名稱字串,預設'test';含孤立代理字元者建構時throw</p></td>
356
367
  </tr>
357
368
 
358
369
 
@@ -554,7 +565,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
554
565
 
555
566
  <dt class="tag-source">Source:</dt>
556
567
  <dd class="tag-source"><ul class="dummy"><li>
557
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line1188">line 1188</a>
568
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line1289">line 1289</a>
558
569
  </li></ul></dd>
559
570
 
560
571
 
@@ -732,7 +743,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
732
743
 
733
744
  <dt class="tag-source">Source:</dt>
734
745
  <dd class="tag-source"><ul class="dummy"><li>
735
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line1005">line 1005</a>
746
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line1103">line 1103</a>
736
747
  </li></ul></dd>
737
748
 
738
749
 
@@ -898,7 +909,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
898
909
 
899
910
  <dt class="tag-source">Source:</dt>
900
911
  <dd class="tag-source"><ul class="dummy"><li>
901
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line699">line 699</a>
912
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line774">line 774</a>
902
913
  </li></ul></dd>
903
914
 
904
915
 
@@ -1063,7 +1074,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
1063
1074
 
1064
1075
  <dt class="tag-source">Source:</dt>
1065
1076
  <dd class="tag-source"><ul class="dummy"><li>
1066
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line561">line 561</a>
1077
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line632">line 632</a>
1067
1078
  </li></ul></dd>
1068
1079
 
1069
1080
 
@@ -1352,7 +1363,7 @@ autoInsert為false而主鍵不存在(不會寫入)者亦先經同一檢核;合
1352
1363
 
1353
1364
  <dt class="tag-source">Source:</dt>
1354
1365
  <dd class="tag-source"><ul class="dummy"><li>
1355
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line806">line 806</a>
1366
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line884">line 884</a>
1356
1367
  </li></ul></dd>
1357
1368
 
1358
1369
 
@@ -1642,7 +1653,7 @@ autoInsert為false而主鍵不存在(不會寫入)者亦先經同一檢核;合
1642
1653
 
1643
1654
  <dt class="tag-source">Source:</dt>
1644
1655
  <dd class="tag-source"><ul class="dummy"><li>
1645
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line497">line 497</a>
1656
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line568">line 568</a>
1646
1657
  </li></ul></dd>
1647
1658
 
1648
1659
 
@@ -1802,7 +1813,7 @@ autoInsert為false而主鍵不存在(不會寫入)者亦先經同一檢核;合
1802
1813
 
1803
1814
  <dt class="tag-source">Source:</dt>
1804
1815
  <dd class="tag-source"><ul class="dummy"><li>
1805
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line429">line 429</a>
1816
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line500">line 500</a>
1806
1817
  </li></ul></dd>
1807
1818
 
1808
1819
 
@@ -1987,7 +1998,7 @@ select亦納入序列化,令其不致與寫入之整檔替換交錯。</p>
1987
1998
 
1988
1999
  <dt class="tag-source">Source:</dt>
1989
2000
  <dd class="tag-source"><ul class="dummy"><li>
1990
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line133">line 133</a>
2001
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line164">line 164</a>
1991
2002
  </li></ul></dd>
1992
2003
 
1993
2004
 
@@ -2071,7 +2082,7 @@ select亦納入序列化,令其不致與寫入之整檔替換交錯。</p>
2071
2082
  <br class="clear">
2072
2083
 
2073
2084
  <footer>
2074
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Oct 20 2026 12:54:27 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
2085
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Oct 21 2026 00:43:38 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
2075
2086
  </footer>
2076
2087
 
2077
2088
  <script>prettyPrint();</script>
@@ -67,6 +67,8 @@ import iseobj from 'wsemi/src/iseobj.mjs'
67
67
  import isearr from 'wsemi/src/isearr.mjs'
68
68
  import haskey from 'wsemi/src/haskey.mjs'
69
69
  import cvOrmFind from 'wsemi/src/cvOrmFind.mjs'
70
+ import chkOrmStr from 'wsemi/src/chkOrmStr.mjs'
71
+ import chkOrmData from 'wsemi/src/chkOrmData.mjs'
70
72
 
71
73
 
72
74
  //kpGqueue, 以資料庫檔案路徑為鍵之全域序列化佇列, 詳見下方serialize說明
@@ -100,11 +102,21 @@ let kpGqueue = {}
100
102
  * 故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。
101
103
  * 註: 數據以JSON存檔,Date存入後為ISO字串;insert、insertBulk與save寫入之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject,詳見各函數與README。
102
104
  *
105
+ * 字串良構(w-orm系列規則,wsemi之chkOrmStr與chkOrmData): lowdb以JSON檔存放,孤立代理字元(String.prototype.isWellFormed()為false)可保真,本身不致相撞,
106
+ * 惟系列其他後端(UTF-8、BSON儲存)會改寫為U+FFFD,為使更換後端時資料行為一致,全系列一律於任何讀取與寫入之前拒收:
107
+ * 1. 主鍵(id)不得含孤立代理字元與NUL,不隨autoGenPk改變;
108
+ * 2. 寫入資料(insert、insertBulk、save)之字串值與鍵名(含巢狀物件、陣列元素、Map與Set之項目、Error與RegExp之文字)不得含孤立代理字元,值與鍵名之NUL不拒;
109
+ * 3. opt.url、opt.db、opt.cl含孤立代理字元者於建構時throw,訊息為invalid opt.&lt;名>: string contains lone surrogate。
110
+ * 處置與無效主鍵同形:insert與insertBulk整批reject而不寫入任何一筆(error事件先於reject),save與del該筆ok為0並附err(同批其他筆照常),selectByPk回null;
111
+ * 訊息為固定詞彙而不回顯值:invalid id: 原因 at [data[k].id]、invalid data: 原因 at [data[k].路徑](k為批次索引,單一物件為0;不合格之鍵名以&lt;invalid key>標示),
112
+ * 原因為string contains lone surrogate、string contains NUL(僅主鍵)、not a string或string is empty(del之id未給、非字串或空字串)。
113
+ * U+FFFD本身與成對代理(emoji等)為良構,照常接受。既有資料不遷移,檢出與清除法見README。
114
+ *
103
115
  * @class
104
116
  * @param {Object} [opt={}] 輸入設定物件,預設{}
105
- * @param {String} [opt.url='./db.json'] 輸入資料庫位置字串,預設'./db.json'
106
- * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
107
- * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
117
+ * @param {String} [opt.url='./db.json'] 輸入資料庫位置字串,預設'./db.json';含孤立代理字元者建構時throw
118
+ * @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm';含孤立代理字元者建構時throw
119
+ * @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test';含孤立代理字元者建構時throw
108
120
  * @param {Boolean} [opt.useCache=false] 輸入是否使用select快取,適用於單程序操作,預設false
109
121
  * @param {Boolean} [opt.autoGenPk=true] 輸入insert、insertBulk與save於數據未帶有效主鍵(本套件為id欄位)時是否自動產生主鍵值,預設true;給false代表主鍵改由呼叫端自備,套件不產生亦不檢查其唯一性與格式,未帶有效主鍵者將reject
110
122
  * @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
@@ -114,23 +126,42 @@ function WOrmLowdb(opt = {}) {
114
126
  //_cache
115
127
  let _cache = null
116
128
 
129
+ //chkOptStr, 儲存位址名稱(opt.url、opt.db、opt.cl)之良構檢核, 孤立代理字元者於建構時同步throw, 先於建立任何檔案或讀寫:
130
+ //url為檔案路徑, 經檔案系統UTF-8化後孤立代理被改寫為U+FFFD而落到他檔; db與cl雖為JSON內之鍵可保真, 全系列仍一律拒收(同一應用更換後端時行為一致);
131
+ //名稱不檢NUL(opt.nul為false), 訊息不回顯值
132
+ let chkOptStr = (name, v) => {
133
+ let r = chkOrmStr(v, { nul: false })
134
+ if (r !== null) {
135
+ throw new Error(`invalid opt.${name}: ${r}`)
136
+ }
137
+ }
138
+
117
139
  //url
118
140
  let url = get(opt, 'url')
119
141
  if (!isestr(url)) {
120
142
  url = './db.json'
121
143
  }
144
+ else {
145
+ chkOptStr('url', url)
146
+ }
122
147
 
123
148
  //db
124
149
  let db = get(opt, 'db')
125
150
  if (!isestr(db)) {
126
151
  db = 'worm'
127
152
  }
153
+ else {
154
+ chkOptStr('db', db)
155
+ }
128
156
 
129
157
  //cl
130
158
  let cl = get(opt, 'cl')
131
159
  if (!isestr(cl)) {
132
160
  cl = 'test'
133
161
  }
162
+ else {
163
+ chkOptStr('cl', cl)
164
+ }
134
165
 
135
166
  //useCache
136
167
  let useCache = get(opt, 'useCache')
@@ -405,6 +436,45 @@ function WOrmLowdb(opt = {}) {
405
436
  return data
406
437
  }
407
438
 
439
+ //chkPk, 主鍵之檢核, 回傳null(合格)或固定原因字串(不回顯值), 供寫入前(procWellFormed)、selectByPk與del共用:
440
+ //空字串非有效主鍵(同isestr); 其餘依wsemi之chkOrmStr: 非字串、含NUL(主鍵併入NUL, 與find同標準)、含孤立代理字元
441
+ //(lowdb以JSON存檔可保真, 但全系列一律拒收, 使同一應用更換後端時資料行為一致; 孤立代理字元之主鍵於其他後端會被改寫為U+FFFD而命中他筆)
442
+ let chkPk = (id) => {
443
+ if (id === '') {
444
+ return 'string is empty'
445
+ }
446
+ return chkOrmStr(id)
447
+ }
448
+
449
+ //getWellFormedMsg, 檢核第k筆之主鍵與資料, 回傳null(合格)或錯誤訊息; 訊息為固定詞彙而不回顯值, 路徑含批次索引(單一物件呼叫之索引為data[0]),
450
+ //與wsemi之cvOrmFind及案例表同詞彙:
451
+ // invalid id: &lt;原因> at [data[&lt;k>].id]
452
+ // invalid data: &lt;原因> at [data[&lt;k>].&lt;路徑>]
453
+ //1.作用於procPk之後(已cloneDeep並補主鍵)之複本, 且其後之判定、寫入皆只用該複本, 檢查之值即使用之值; 自有存取器於cloneDeep時已轉為資料屬性
454
+ //2.同一筆主鍵先於資料; 資料(chkOrmData)檢鍵名與字串值(含巢狀物件、陣列、Map、Set、Error與RegExp之文字)之孤立代理字元, 值與鍵名之NUL不拒
455
+ //3.不隨autoGenPk改變: 如實存放性檢核, 非格式偏好
456
+ let getWellFormedMsg = (v, k) => {
457
+ let r = chkPk(v.id)
458
+ if (r !== null) {
459
+ return `invalid id: ${r} at [data[${k}].id]`
460
+ }
461
+ let e = chkOrmData(v, { root: `data[${k}]` })
462
+ if (e !== null) {
463
+ return `invalid data: ${e.reason} at [${e.path}]`
464
+ }
465
+ return null
466
+ }
467
+
468
+ //procWellFormed, 供insert與insertBulk於任何寫入之前檢核各筆: 任一筆不合格即拋出為整批性錯誤(以索引最小者為準)而不寫入任何一筆
469
+ let procWellFormed = (data) => {
470
+ each(data, function(v, k) {
471
+ let msg = getWellFormedMsg(v, k)
472
+ if (msg !== null) {
473
+ throw new Error(msg)
474
+ }
475
+ })
476
+ }
477
+
408
478
  //readData, 讀取資料庫檔案並取得本資料表之陣列
409
479
  //註: 使用lowdb.read()初始化後須馬上檢測, 若無key則須先創建空陣列
410
480
  let readData = async() => {
@@ -429,14 +499,15 @@ function WOrmLowdb(opt = {}) {
429
499
  }
430
500
  }
431
501
 
432
- //getKpPk, 由數據陣列建立[主鍵值 => {k, v}]之對照表, k為所在索引
502
+ //getKpPk, 由數據陣列建立[主鍵值 => {k, v}]之Map, k為所在索引
433
503
  //僅收錄具有效主鍵值者, 令資料表內若有主鍵值無效之殘留數據不致被誤配為命中
504
+ //須用Map而非一般物件加in(haskey)或lodash get: 主鍵為constructor、toString、__proto__等Object.prototype成員名時, 走原型鏈恆有值(帳本R-PKMAP-01)
434
505
  let getKpPk = (ltdt) => {
435
- let kp = {}
506
+ let kp = new Map()
436
507
  each(ltdt, (v, k) => {
437
508
  let id = get(v, 'id')
438
509
  if (isestr(id)) {
439
- kp[id] = { k, v }
510
+ kp.set(id, { k, v })
440
511
  }
441
512
  })
442
513
  return kp
@@ -548,8 +619,8 @@ function WOrmLowdb(opt = {}) {
548
619
  let res = null
549
620
  try {
550
621
 
551
- //check
552
- if (!isestr(pk)) {
622
+ //check, 含NUL或孤立代理字元之主鍵亦屬無效(chkPk): 此類主鍵不可能由本套件寫入(寫入側已拒收), 回null而不reject, 不發出error事件
623
+ if (!isestr(pk) || chkPk(pk) !== null) {
553
624
  //未給有效主鍵值視為查無數據
554
625
  return null
555
626
  }
@@ -559,7 +630,7 @@ function WOrmLowdb(opt = {}) {
559
630
 
560
631
  //查找資料表內pk, 判定基準與insert、insertBulk、save、del內對既有數據之認定一致(皆以id之字串值比對)
561
632
  let kp = getKpPk(ltdt)
562
- let r = get(kp, pk, null)
633
+ let r = kp.get(pk)
563
634
 
564
635
  //check
565
636
  if (iseobj(r)) {
@@ -645,6 +716,10 @@ function WOrmLowdb(opt = {}) {
645
716
  //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01)
646
717
  chkDataDate(data)
647
718
 
719
+ //check well-formed, 主鍵(併入NUL)與資料之孤立代理字元, 須於任何讀取與寫入之前完成, 只用上方cloneDeep之複本;
720
+ //置於Date檢核之後: 兩者並存時先報Date(既有行為與測試不變)
721
+ procWellFormed(data)
722
+
648
723
  //read
649
724
  let arrOld = await readData()
650
725
 
@@ -658,14 +733,14 @@ function WOrmLowdb(opt = {}) {
658
733
  each(data, (v) => {
659
734
 
660
735
  //check
661
- if (!haskey(kp, v.id)) {
736
+ if (!kp.has(v.id)) {
662
737
  //未存在v.id
663
738
 
664
739
  //push
665
740
  ltpush.push(v)
666
741
 
667
742
  //update, 須同步更新kp, 否則同批含重複主鍵者會全數寫入而出現重複主鍵
668
- kp[v.id] = { k: -1, v }
743
+ kp.set(v.id, { k: -1, v })
669
744
 
670
745
  ltdone.push(true)
671
746
  }
@@ -773,6 +848,9 @@ function WOrmLowdb(opt = {}) {
773
848
  //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01)
774
849
  chkDataDate(data)
775
850
 
851
+ //check well-formed, 同insert(置於Date檢核之後)
852
+ procWellFormed(data)
853
+
776
854
  //read
777
855
  let arrOld = await readData()
778
856
 
@@ -786,11 +864,11 @@ function WOrmLowdb(opt = {}) {
786
864
  //同批含重複主鍵者亦於此被偵測為衝突(kp隨檢查同步更新)
787
865
  let pkConflict = null
788
866
  each(data, (v) => {
789
- if (haskey(kp, v.id)) {
867
+ if (kp.has(v.id)) {
790
868
  pkConflict = v.id
791
869
  return false //中止each
792
870
  }
793
- kp[v.id] = { k: -1, v }
871
+ kp.set(v.id, { k: -1, v })
794
872
  })
795
873
 
796
874
  //check
@@ -879,6 +957,12 @@ function WOrmLowdb(opt = {}) {
879
957
  //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01); 合併與覆寫路徑之Date皆來自此複本
880
958
  chkDataDate(data)
881
959
 
960
+ //msgs, 各筆主鍵(併入NUL)與資料之良構檢核結果(合格者為null), 於任何讀取與寫入之前完成, 只用上方cloneDeep之複本;
961
+ //save為逐筆函數, 不合格者僅該筆失敗(ok為0並附err), 同批其他筆照常處理
962
+ let msgs = map(data, function(v, k) {
963
+ return getWellFormedMsg(v, k)
964
+ })
965
+
882
966
  //read
883
967
  let arrOld = await readData()
884
968
 
@@ -888,17 +972,22 @@ function WOrmLowdb(opt = {}) {
888
972
  //kp
889
973
  let kp = getKpPk(arrNew)
890
974
 
891
- //pmSeries
975
+ //pmSeries, 回呼之第二參數為批次索引
892
976
  let b = false
893
- res = await pmSeries(data, async(v) => {
977
+ res = await pmSeries(data, async(v, k) => {
894
978
 
895
979
  //rest
896
980
  let rest = null
897
981
 
898
982
  try {
899
983
 
984
+ //不合格者以該筆失敗回報(下方catch組成ok為0之結果), 先於該筆之任何讀取與寫入
985
+ if (msgs[k] !== null) {
986
+ throw new Error(msgs[k])
987
+ }
988
+
900
989
  //查找資料表內v.id
901
- let r = get(kp, v.id, null)
990
+ let r = kp.get(v.id)
902
991
 
903
992
  //existed
904
993
  let existed = iseobj(r)
@@ -910,8 +999,17 @@ function WOrmLowdb(opt = {}) {
910
999
  //vv, 現值
911
1000
  let vv = r.v
912
1001
 
1002
+ //vIn, 以merge(lodash)讀取v一次所得之純資料物件, 檢核之並以之合併: merge以keysIn取值, 會讀到繼承之可列舉存取器(類別實例之原型上),
1003
+ //其每次讀取可回不同值, 上方對複本之檢核與此處合併所讀者未必相同, 故檢查之值即合併之值; 不合格者該筆ok為0(尚未寫入arrNew)
1004
+ //只檢核來自輸入者(vIn), 不檢核現值vv之既有內容: 舊資料之孤立代理字元不遷移(lowdb保真而留存), 不得使對該列之無關更新被拒
1005
+ let vIn = merge({}, v)
1006
+ let eIn = chkOrmData(vIn, { root: `data[${k}]` })
1007
+ if (eIn !== null) {
1008
+ throw new Error(`invalid data: ${eIn.reason} at [${eIn.path}]`)
1009
+ }
1010
+
913
1011
  //vm, 合併後之結果, 另建新物件以免污染原數據
914
- let vm = merge({}, vv, v)
1012
+ let vm = merge({}, vv, vIn)
915
1013
 
916
1014
  //判定基準為[合併後結果與現值相同], 而非[待寫入物件與現值全等],
917
1015
  //令nModified忠實反映是否真的寫入, 僅給部份欄位且值皆相同者不應回報已修改
@@ -929,7 +1027,7 @@ function WOrmLowdb(opt = {}) {
929
1027
 
930
1028
  //update
931
1029
  arrNew[r.k] = vm
932
- kp[v.id] = { k: r.k, v: vm }
1030
+ kp.set(v.id, { k: r.k, v: vm })
933
1031
 
934
1032
  b = true
935
1033
 
@@ -949,7 +1047,7 @@ function WOrmLowdb(opt = {}) {
949
1047
  //push
950
1048
  let k = size(arrNew)
951
1049
  arrNew.push(v)
952
- kp[v.id] = { k, v }
1050
+ kp.set(v.id, { k, v })
953
1051
 
954
1052
  b = true
955
1053
 
@@ -1075,10 +1173,10 @@ function WOrmLowdb(opt = {}) {
1075
1173
  //kp
1076
1174
  let kp = getKpPk(arrOld)
1077
1175
 
1078
- //pmSeries
1176
+ //pmSeries, 回呼之第二參數為批次索引
1079
1177
  let kpDel = {}
1080
1178
  let b = false
1081
- res = await pmSeries(data, async(v) => {
1179
+ res = await pmSeries(data, async(v, k) => {
1082
1180
 
1083
1181
  //rest
1084
1182
  let rest = null
@@ -1086,24 +1184,27 @@ function WOrmLowdb(opt = {}) {
1086
1184
  try {
1087
1185
 
1088
1186
  //id
1089
- let id = get(v, 'id', '')
1187
+ let id = get(v, 'id')
1188
+
1189
+ //check, 主鍵之檢核(chkPk): 非字串、空字串、含NUL、含孤立代理字元者皆無效
1190
+ let msgPk = chkPk(id)
1090
1191
 
1091
1192
  //check
1092
- if (!isestr(id)) {
1193
+ if (msgPk !== null) {
1093
1194
  //未給有效v.id視為該筆數據有問題而無法處理, 以ok為0並附err回報,
1094
- //與[已給v.id但查無數據]之ok為1有別, 二者須可由ok分辨
1195
+ //與[已給v.id但查無數據]之ok為1有別, 二者須可由ok分辨; 訊息為固定詞彙, 含批次索引而不回顯值
1095
1196
  //註: 亦不可將無效主鍵送進查詢條件, 故於此直接定案
1096
1197
  rest = {
1097
1198
  n: 0,
1098
1199
  nDeleted: 0,
1099
1200
  ok: 0,
1100
- err: `can not delete by invalid id[${id}]`,
1201
+ err: `invalid id: ${msgPk} at [data[${k}].id]`,
1101
1202
  }
1102
1203
  }
1103
1204
  else {
1104
1205
 
1105
1206
  //查找資料表內v.id
1106
- let r = get(kp, id, null)
1207
+ let r = kp.get(id)
1107
1208
 
1108
1209
  //check
1109
1210
  if (iseobj(r)) {
@@ -1113,7 +1214,7 @@ function WOrmLowdb(opt = {}) {
1113
1214
  kpDel[r.k] = true
1114
1215
 
1115
1216
  //update, 須同步移除, 否則同批重複主鍵者會重複計入nDeleted
1116
- delete kp[id]
1217
+ kp.delete(id)
1117
1218
 
1118
1219
  b = true
1119
1220
 
@@ -1350,7 +1451,7 @@ export default WOrmLowdb
1350
1451
  <br class="clear">
1351
1452
 
1352
1453
  <footer>
1353
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Oct 20 2026 12:54:27 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1454
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Oct 21 2026 00:43:38 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1354
1455
  </footer>
1355
1456
 
1356
1457
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -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 Tue Oct 20 2026 12:54: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 Wed Oct 21 2026 00:43:38 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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "w-orm-lowdb",
3
- "version": "1.0.36",
3
+ "version": "1.0.37",
4
4
  "main": "dist/w-orm-lowdb.umd.js",
5
5
  "dependencies": {
6
6
  "lowdb": "^7.0.1",
7
7
  "mingo": "^7.2.4",
8
- "wsemi": "^1.9.21"
8
+ "wsemi": "^1.9.24"
9
9
  },
10
10
  "devDependencies": {
11
11
  "w-package-tools": "^1.1.21"