w-orm-lowdb 1.0.33 → 1.0.34

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.
@@ -91,13 +91,14 @@ err為錯誤訊息字串,內容與正規管道所送出者一致。
91
91
  註: 逐筆失敗時整批仍resolve,故收到本事件不表示該次呼叫失敗;正常結果(如查無數據、主鍵未命中、全數已存在)不發出本事件。</p>
92
92
  <p>註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵。
93
93
  註: 本套件之寫入為[整檔讀出、記憶體修改、整檔寫回],其原子性由行程內之序列化佇列提供,
94
- 故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。</p></li></ul></dd>
94
+ 故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。
95
+ 註: 數據以JSON存檔,Date存入後為ISO字串;insert、insertBulk與save寫入之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject,詳見各函數與README。</p></li></ul></dd>
95
96
 
96
97
 
97
98
 
98
99
  <dt class="tag-source">Source:</dt>
99
100
  <dd class="tag-source"><ul class="dummy"><li>
100
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line62">line 62</a>
101
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line63">line 63</a>
101
102
  </li></ul></dd>
102
103
 
103
104
 
@@ -540,8 +541,9 @@ null僅可用於欄位等值、$eq、$ne。白名單外之運算子($not $nor $r
540
541
  欄名同於Object.prototype成員(constructor、toString等)者,數據無此自有欄位時視為缺欄;
541
542
  Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦會比較):條件內任何位置之Date其年份須介於0000-9999,
542
543
  範圍外(年份小於0或大於9999,如new Date(8.64e15)、new Date(-8.64e15))整批reject,訊息為invalid find: Date year is out of range 0000-9999 at [路徑],
543
- 不回顯值,不讀檔、不刪除任何數據;數據側已存入之範圍外Date(如以new Date(8.64e15)作永不過期哨兵)寫入行為不變,
544
- 但此類資料無法以Date條件正確比較(例如{t:{$lt:現在}}會命中哨兵並將其刪除),見README。
544
+ 不回顯值,不讀檔、不刪除任何數據;數據側自1.0.34起insert、insertBulk與save寫入時已拒收範圍外Date,
545
+ 但資料庫檔內既有之範圍外Date字串(舊版以new Date(8.64e15)作永不過期哨兵存入者)仍無法以Date條件正確比較
546
+ (例如{t:{$lt:現在}}會命中哨兵並將其刪除),請自行清理或改寫,見README。
545
547
  需明確表達時可用可攜寫法: 排除NULL為{$and:[{f:{$ne:null}},{f:{$ne:x}}]},含NULL為{$or:[{f:null},{f:{$ne:x}}]}。</li>
546
548
  </ol>
547
549
  <p>n與nDeleted皆為實際刪除筆數,不取全表筆數。條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤。</p></li></ul></dd>
@@ -550,7 +552,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
550
552
 
551
553
  <dt class="tag-source">Source:</dt>
552
554
  <dd class="tag-source"><ul class="dummy"><li>
553
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line1091">line 1091</a>
555
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line1178">line 1178</a>
554
556
  </li></ul></dd>
555
557
 
556
558
 
@@ -728,7 +730,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
728
730
 
729
731
  <dt class="tag-source">Source:</dt>
730
732
  <dd class="tag-source"><ul class="dummy"><li>
731
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line909">line 909</a>
733
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line995">line 995</a>
732
734
  </li></ul></dd>
733
735
 
734
736
 
@@ -887,13 +889,14 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
887
889
  註: 本函數非insert之加速版,兩者衝突政策不同。insert於主鍵已存在時跳過該筆而整批ok為1,
888
890
  本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
889
891
  註: 於本套件不會較insert快,因兩者皆為一次讀檔與一次寫檔,
890
- 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫</p></li></ul></dd>
892
+ 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
893
+ 註: 數據內任何深度之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject且不寫入任何一筆,規則同insert,詳見insert與README</p></li></ul></dd>
891
894
 
892
895
 
893
896
 
894
897
  <dt class="tag-source">Source:</dt>
895
898
  <dd class="tag-source"><ul class="dummy"><li>
896
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line612">line 612</a>
899
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line689">line 689</a>
897
900
  </li></ul></dd>
898
901
 
899
902
 
@@ -1048,13 +1051,17 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
1048
1051
  <dl class="details">
1049
1052
 
1050
1053
  <dt class="tag-description">Description:</dt>
1051
- <dd class="tag-description"><ul class="dummy"><li><p>插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫</p></li></ul></dd>
1054
+ <dd class="tag-description"><ul class="dummy"><li><p>插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫</p>
1055
+ <p>數據內任何深度(頂層欄位、巢狀物件、陣列內)之Date年份須介於0000-9999(序列化為固定24字元之ISO字串,字典序即時間序),
1056
+ 範圍外(年份小於0或大於9999,如以new Date(8.64e15)作永不過期哨兵)整批reject且不寫入任何一筆,訊息為invalid data: Date year is out of range 0000-9999 at [路徑],
1057
+ 不回顯值,於reject之前發出一次error事件、不發出change事件;無效Date維持序列化為null存入;永不過期請改用null或new Date('9999-12-31T23:59:59.999Z');
1058
+ 資料庫檔內既有之範圍外Date字串不遷移,仍無法以Date條件正確比較,詳見README</p></li></ul></dd>
1052
1059
 
1053
1060
 
1054
1061
 
1055
1062
  <dt class="tag-source">Source:</dt>
1056
1063
  <dd class="tag-source"><ul class="dummy"><li>
1057
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line478">line 478</a>
1064
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line551">line 551</a>
1058
1065
  </li></ul></dd>
1059
1066
 
1060
1067
 
@@ -1335,13 +1342,15 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
1335
1342
  <dl class="details">
1336
1343
 
1337
1344
  <dt class="tag-description">Description:</dt>
1338
- <dd class="tag-description"><ul class="dummy"><li><p>儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且autoInsert為true時改為插入</p></li></ul></dd>
1345
+ <dd class="tag-description"><ul class="dummy"><li><p>儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且autoInsert為true時改為插入</p>
1346
+ <p>數據內任何深度之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject且不寫入任何一筆(逐筆失敗之ok為0機制不適用),規則同insert,詳見insert與README;
1347
+ autoInsert為false而主鍵不存在(不會寫入)者亦先經同一檢核;合併與覆寫路徑寫入之Date皆來自已檢核之數據,已存在於資料庫之字串不檢核</p></li></ul></dd>
1339
1348
 
1340
1349
 
1341
1350
 
1342
1351
  <dt class="tag-source">Source:</dt>
1343
1352
  <dd class="tag-source"><ul class="dummy"><li>
1344
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line713">line 713</a>
1353
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line796">line 796</a>
1345
1354
  </li></ul></dd>
1346
1355
 
1347
1356
 
@@ -1631,7 +1640,7 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
1631
1640
 
1632
1641
  <dt class="tag-source">Source:</dt>
1633
1642
  <dd class="tag-source"><ul class="dummy"><li>
1634
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line419">line 419</a>
1643
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line487">line 487</a>
1635
1644
  </li></ul></dd>
1636
1645
 
1637
1646
 
@@ -1785,13 +1794,13 @@ Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦
1785
1794
  <dt class="tag-description">Description:</dt>
1786
1795
  <dd class="tag-description"><ul class="dummy"><li><p>查詢數據</p>
1787
1796
  <p>條件find僅支援系列白名單(規則同delAll之第1-3點,見delAll),與delAll共用同一檢核,select(find)命中之數據即delAll(find)刪除之數據
1788
- 條件內之Date年份須介於0000-9999,範圍外整批reject;數據側已存入之範圍外Date無法以Date條件正確比較(規則同delAll之第6點,見README)</p></li></ul></dd>
1797
+ 條件內之Date年份須介於0000-9999,範圍外整批reject;資料庫檔內既有之範圍外Date字串(1.0.34起寫入已拒收範圍外Date)無法以Date條件正確比較(規則同delAll之第6點,見README)</p></li></ul></dd>
1789
1798
 
1790
1799
 
1791
1800
 
1792
1801
  <dt class="tag-source">Source:</dt>
1793
1802
  <dd class="tag-source"><ul class="dummy"><li>
1794
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line351">line 351</a>
1803
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line419">line 419</a>
1795
1804
  </li></ul></dd>
1796
1805
 
1797
1806
 
@@ -1976,7 +1985,7 @@ select亦納入序列化,令其不致與寫入之整檔替換交錯。</p>
1976
1985
 
1977
1986
  <dt class="tag-source">Source:</dt>
1978
1987
  <dd class="tag-source"><ul class="dummy"><li>
1979
- <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line130">line 130</a>
1988
+ <a href="WOrmLowdb.mjs.html">WOrmLowdb.mjs</a>, <a href="WOrmLowdb.mjs.html#line131">line 131</a>
1980
1989
  </li></ul></dd>
1981
1990
 
1982
1991
 
@@ -2060,7 +2069,7 @@ select亦納入序列化,令其不致與寫入之整檔替換交錯。</p>
2060
2069
  <br class="clear">
2061
2070
 
2062
2071
  <footer>
2063
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Oct 19 2026 20:07:22 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
2072
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Oct 19 2026 21:48:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
2064
2073
  </footer>
2065
2074
 
2066
2075
  <script>prettyPrint();</script>
@@ -96,6 +96,7 @@ let kpGqueue = {}
96
96
  * 註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵。
97
97
  * 註: 本套件之寫入為[整檔讀出、記憶體修改、整檔寫回],其原子性由行程內之序列化佇列提供,
98
98
  * 故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。
99
+ * 註: 數據以JSON存檔,Date存入後為ISO字串;insert、insertBulk與save寫入之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject,詳見各函數與README。
99
100
  *
100
101
  * @class
101
102
  * @param {Object} [opt={}] 輸入設定物件,預設{}
@@ -213,6 +214,73 @@ function WOrmLowdb(opt = {}) {
213
214
  return s.length > 120 ? `...${s.slice(-100)}` : s
214
215
  }
215
216
 
217
+ //reIdent, 數據之鍵為一般識別字者於路徑以.鍵表示, 其餘以括號法表示
218
+ let reIdent = /^[\p{L}_$][\p{L}\p{M}\p{N}_$]*$/u
219
+
220
+ //pathSeg, 數據之鍵於路徑之表示: 數據之鍵不受條件白名單之欄名文法約束(可含空白,點,括號,引號,換行,孤立代理碼元),
221
+ //故非一般識別字者以JSON字串括號法表示, 過長者依pathKey截短
222
+ let pathSeg = (k) => {
223
+ let t = pathKey(k)
224
+ return reIdent.test(k) ? `.${t}` : `[${JSON.stringify(t)}]`
225
+ }
226
+
227
+ //isDateNode, 是否為JSON序列化時會經toJSON轉為日期字串之Date: 須有可呼叫之toJSON(此判斷同時避免對每個物件節點都以拋錯探測內部槽),
228
+ //且以Date.prototype.getTime證明內部槽為Date(不受Symbol.toStringTag、instanceof、被覆寫之getTime影響, 跨realm之Date亦可)
229
+ let isDateNode = (v) => {
230
+ if (typeof v.toJSON !== 'function') {
231
+ return false
232
+ }
233
+ try {
234
+ Date.prototype.getTime.call(v)
235
+ return true
236
+ }
237
+ catch (err) {
238
+ return false
239
+ }
240
+ }
241
+
242
+ //chkDataDate, 寫入前之Date範圍檢核(帳本R-DATE-01), 供insert、insertBulk與save於cloneDeep之後(即檢核實際寫入之複本)、readData之前呼叫:
243
+ //走訪數據複本任何深度之Date, 以JSON.stringify(即寫檔時之序列化, 檢查之值即使用之值)判定, 序列化為字串者須為reIsoDate之形(年份0000-9999),
244
+ //範圍外(年份&lt;0或>9999)者fail-closed: 拋出invalid data(由呼叫端既有catch走emitError後reject, 整批不寫檔), 不回顯值;
245
+ //無效Date序列化為null故不檢(既有處置不變); 已是字串者、非Date之toJSON物件(含Buffer)與Map/Set內之Date不檢, 其餘ArrayBuffer視圖(無toJSON者, 序列化時其自有屬性照常輸出)依一般物件走訪;
246
+ //ks為由根起之鍵序列(陣列索引記number, 物件之鍵記string, 僅於拋出時才組成路徑), seen為已走訪物件集合(進入時加入、不移除):
247
+ //循環參照遇回邊即略過(仍由寫檔時之JSON序列化報錯), 共用參照(菱形圖)亦只走訪一次, 時間為O(節點+邊)
248
+ let chkDataDate = (v, ks = [], seen = new WeakSet()) => {
249
+ if (v === null || typeof v !== 'object' || seen.has(v)) {
250
+ return
251
+ }
252
+ if (ArrayBuffer.isView(v) &amp;&amp; typeof v.toJSON === 'function') {
253
+
254
+ //Buffer: JSON序列化取其toJSON之輸出({type,data}), 不含自有屬性, 不走訪(避免逐位元組走訪)
255
+ return
256
+ }
257
+ if (isDateNode(v)) {
258
+ let s = JSON.stringify(v)
259
+ if (typeof s === 'string' &amp;&amp; s.charAt(0) === '"' &amp;&amp; !reIsoDate.test(s.slice(1, -1))) {
260
+ let p = `data${ks.map((k) => (typeof k === 'number' ? `[${k}]` : pathSeg(k))).join('')}`
261
+ throw new Error(`invalid data: Date year is out of range 0000-9999 at [${pathStr(p)}]`)
262
+ }
263
+ return
264
+ }
265
+ seen.add(v)
266
+ if (Array.isArray(v)) {
267
+
268
+ //陣列以索引走訪(JSON序列化亦逐索引, 空洞為null)
269
+ for (let i = 0; i &lt; v.length; i++) {
270
+ ks.push(i)
271
+ chkDataDate(v[i], ks, seen)
272
+ ks.pop()
273
+ }
274
+ }
275
+ else {
276
+ for (let k of Object.keys(v)) {
277
+ ks.push(k)
278
+ chkDataDate(v[k], ks, seen)
279
+ ks.pop()
280
+ }
281
+ }
282
+ }
283
+
216
284
  //isoFind, 把cvOrmFind所回傳複本內之Date就地轉為ISO字串(轉譯層): 數據以JSON存檔, Date於寫入時已成toISOString()之字串,
217
285
  //條件內之Date不轉則恆不命中; 複本僅含null原型物件、密集陣列、純量與有效Date(無共享參照、無環), 就地轉換不影響呼叫端之find
218
286
  //轉換所得之字串須為reIsoDate之形(檢查之值即使用之值), 範圍外(年份&lt;0或>9999)者fail-closed: 拋出invalid find(帳本R-FIND-02), 不回顯值, p為路徑
@@ -389,7 +457,7 @@ function WOrmLowdb(opt = {}) {
389
457
  * 查詢數據
390
458
  *
391
459
  * 條件find僅支援系列白名單(規則同delAll之第1-3點,見delAll),與delAll共用同一檢核,select(find)命中之數據即delAll(find)刪除之數據
392
- * 條件內之Date年份須介於0000-9999,範圍外整批reject;數據側已存入之範圍外Date無法以Date條件正確比較(規則同delAll之第6點,見README)
460
+ * 條件內之Date年份須介於0000-9999,範圍外整批reject;資料庫檔內既有之範圍外Date字串(1.0.34起寫入已拒收範圍外Date)無法以Date條件正確比較(規則同delAll之第6點,見README)
393
461
  *
394
462
  * @memberOf WOrmLowdb
395
463
  * @param {Object} [find={}] 輸入查詢條件物件(系列白名單,規則同delAll之第1-3點); 未給、undefined或{}回傳全部數據; 非條件物件或白名單外運算子整批reject
@@ -516,6 +584,11 @@ function WOrmLowdb(opt = {}) {
516
584
  /**
517
585
  * 插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫
518
586
  *
587
+ * 數據內任何深度(頂層欄位、巢狀物件、陣列內)之Date年份須介於0000-9999(序列化為固定24字元之ISO字串,字典序即時間序),
588
+ * 範圍外(年份小於0或大於9999,如以new Date(8.64e15)作永不過期哨兵)整批reject且不寫入任何一筆,訊息為invalid data: Date year is out of range 0000-9999 at [路徑],
589
+ * 不回顯值,於reject之前發出一次error事件、不發出change事件;無效Date維持序列化為null存入;永不過期請改用null或new Date('9999-12-31T23:59:59.999Z');
590
+ * 資料庫檔內既有之範圍外Date字串不遷移,仍無法以Date條件正確比較,詳見README
591
+ *
519
592
  * @memberOf WOrmLowdb
520
593
  * @param {Object|Array} data 輸入數據物件或陣列
521
594
  * @param {Object} [option={}] 輸入設定物件,預設為{}
@@ -559,6 +632,9 @@ function WOrmLowdb(opt = {}) {
559
632
  //check id
560
633
  data = procPk(data, 'insert')
561
634
 
635
+ //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01)
636
+ chkDataDate(data)
637
+
562
638
  //read
563
639
  let arrOld = await readData()
564
640
 
@@ -651,6 +727,7 @@ function WOrmLowdb(opt = {}) {
651
727
  * 本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
652
728
  * 註: 於本套件不會較insert快,因兩者皆為一次讀檔與一次寫檔,
653
729
  * 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
730
+ * 註: 數據內任何深度之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject且不寫入任何一筆,規則同insert,詳見insert與README
654
731
  *
655
732
  * @memberOf WOrmLowdb
656
733
  * @param {Object|Array} data 輸入數據物件或陣列
@@ -683,6 +760,9 @@ function WOrmLowdb(opt = {}) {
683
760
  //check id
684
761
  data = procPk(data, 'insertBulk')
685
762
 
763
+ //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01)
764
+ chkDataDate(data)
765
+
686
766
  //read
687
767
  let arrOld = await readData()
688
768
 
@@ -751,6 +831,9 @@ function WOrmLowdb(opt = {}) {
751
831
  /**
752
832
  * 儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且autoInsert為true時改為插入
753
833
  *
834
+ * 數據內任何深度之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject且不寫入任何一筆(逐筆失敗之ok為0機制不適用),規則同insert,詳見insert與README;
835
+ * autoInsert為false而主鍵不存在(不會寫入)者亦先經同一檢核;合併與覆寫路徑寫入之Date皆來自已檢核之數據,已存在於資料庫之字串不檢核
836
+ *
754
837
  * @memberOf WOrmLowdb
755
838
  * @param {Object|Array} data 輸入數據物件或陣列
756
839
  * @param {Object} [option={}] 輸入設定物件,預設為{}
@@ -783,6 +866,9 @@ function WOrmLowdb(opt = {}) {
783
866
  //check id
784
867
  data = procPk(data, 'save')
785
868
 
869
+ //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01); 合併與覆寫路徑之Date皆來自此複本
870
+ chkDataDate(data)
871
+
786
872
  //read
787
873
  let arrOld = await readData()
788
874
 
@@ -1125,8 +1211,9 @@ function WOrmLowdb(opt = {}) {
1125
1211
  * 欄名同於Object.prototype成員(constructor、toString等)者,數據無此自有欄位時視為缺欄;
1126
1212
  * Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦會比較):條件內任何位置之Date其年份須介於0000-9999,
1127
1213
  * 範圍外(年份小於0或大於9999,如new Date(8.64e15)、new Date(-8.64e15))整批reject,訊息為invalid find: Date year is out of range 0000-9999 at [路徑],
1128
- * 不回顯值,不讀檔、不刪除任何數據;數據側已存入之範圍外Date(如以new Date(8.64e15)作永不過期哨兵)寫入行為不變,
1129
- * 但此類資料無法以Date條件正確比較(例如{t:{$lt:現在}}會命中哨兵並將其刪除),見README。
1214
+ * 不回顯值,不讀檔、不刪除任何數據;數據側自1.0.34起insert、insertBulk與save寫入時已拒收範圍外Date,
1215
+ * 但資料庫檔內既有之範圍外Date字串(舊版以new Date(8.64e15)作永不過期哨兵存入者)仍無法以Date條件正確比較
1216
+ * (例如{t:{$lt:現在}}會命中哨兵並將其刪除),請自行清理或改寫,見README。
1130
1217
  * 需明確表達時可用可攜寫法: 排除NULL為{$and:[{f:{$ne:null}},{f:{$ne:x}}]},含NULL為{$or:[{f:null},{f:{$ne:x}}]}。
1131
1218
  *
1132
1219
  * n與nDeleted皆為實際刪除筆數,不取全表筆數。條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤。
@@ -1253,7 +1340,7 @@ export default WOrmLowdb
1253
1340
  <br class="clear">
1254
1341
 
1255
1342
  <footer>
1256
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Oct 19 2026 20:07:22 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1343
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Oct 19 2026 21:48:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1257
1344
  </footer>
1258
1345
 
1259
1346
  <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 Mon Oct 19 2026 20:07:22 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 Oct 19 2026 21:48:29 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,6 +1,6 @@
1
1
  {
2
2
  "name": "w-orm-lowdb",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "main": "dist/w-orm-lowdb.umd.js",
5
5
  "dependencies": {
6
6
  "lowdb": "^7.0.1",
package/src/WOrmLowdb.mjs CHANGED
@@ -49,6 +49,7 @@ let kpGqueue = {}
49
49
  * 註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵。
50
50
  * 註: 本套件之寫入為[整檔讀出、記憶體修改、整檔寫回],其原子性由行程內之序列化佇列提供,
51
51
  * 故僅單一行程內併發成立,跨行程併發不成立,詳見README之併發保證宣告。
52
+ * 註: 數據以JSON存檔,Date存入後為ISO字串;insert、insertBulk與save寫入之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject,詳見各函數與README。
52
53
  *
53
54
  * @class
54
55
  * @param {Object} [opt={}] 輸入設定物件,預設{}
@@ -166,6 +167,73 @@ function WOrmLowdb(opt = {}) {
166
167
  return s.length > 120 ? `...${s.slice(-100)}` : s
167
168
  }
168
169
 
170
+ //reIdent, 數據之鍵為一般識別字者於路徑以.鍵表示, 其餘以括號法表示
171
+ let reIdent = /^[\p{L}_$][\p{L}\p{M}\p{N}_$]*$/u
172
+
173
+ //pathSeg, 數據之鍵於路徑之表示: 數據之鍵不受條件白名單之欄名文法約束(可含空白,點,括號,引號,換行,孤立代理碼元),
174
+ //故非一般識別字者以JSON字串括號法表示, 過長者依pathKey截短
175
+ let pathSeg = (k) => {
176
+ let t = pathKey(k)
177
+ return reIdent.test(k) ? `.${t}` : `[${JSON.stringify(t)}]`
178
+ }
179
+
180
+ //isDateNode, 是否為JSON序列化時會經toJSON轉為日期字串之Date: 須有可呼叫之toJSON(此判斷同時避免對每個物件節點都以拋錯探測內部槽),
181
+ //且以Date.prototype.getTime證明內部槽為Date(不受Symbol.toStringTag、instanceof、被覆寫之getTime影響, 跨realm之Date亦可)
182
+ let isDateNode = (v) => {
183
+ if (typeof v.toJSON !== 'function') {
184
+ return false
185
+ }
186
+ try {
187
+ Date.prototype.getTime.call(v)
188
+ return true
189
+ }
190
+ catch (err) {
191
+ return false
192
+ }
193
+ }
194
+
195
+ //chkDataDate, 寫入前之Date範圍檢核(帳本R-DATE-01), 供insert、insertBulk與save於cloneDeep之後(即檢核實際寫入之複本)、readData之前呼叫:
196
+ //走訪數據複本任何深度之Date, 以JSON.stringify(即寫檔時之序列化, 檢查之值即使用之值)判定, 序列化為字串者須為reIsoDate之形(年份0000-9999),
197
+ //範圍外(年份<0或>9999)者fail-closed: 拋出invalid data(由呼叫端既有catch走emitError後reject, 整批不寫檔), 不回顯值;
198
+ //無效Date序列化為null故不檢(既有處置不變); 已是字串者、非Date之toJSON物件(含Buffer)與Map/Set內之Date不檢, 其餘ArrayBuffer視圖(無toJSON者, 序列化時其自有屬性照常輸出)依一般物件走訪;
199
+ //ks為由根起之鍵序列(陣列索引記number, 物件之鍵記string, 僅於拋出時才組成路徑), seen為已走訪物件集合(進入時加入、不移除):
200
+ //循環參照遇回邊即略過(仍由寫檔時之JSON序列化報錯), 共用參照(菱形圖)亦只走訪一次, 時間為O(節點+邊)
201
+ let chkDataDate = (v, ks = [], seen = new WeakSet()) => {
202
+ if (v === null || typeof v !== 'object' || seen.has(v)) {
203
+ return
204
+ }
205
+ if (ArrayBuffer.isView(v) && typeof v.toJSON === 'function') {
206
+
207
+ //Buffer: JSON序列化取其toJSON之輸出({type,data}), 不含自有屬性, 不走訪(避免逐位元組走訪)
208
+ return
209
+ }
210
+ if (isDateNode(v)) {
211
+ let s = JSON.stringify(v)
212
+ if (typeof s === 'string' && s.charAt(0) === '"' && !reIsoDate.test(s.slice(1, -1))) {
213
+ let p = `data${ks.map((k) => (typeof k === 'number' ? `[${k}]` : pathSeg(k))).join('')}`
214
+ throw new Error(`invalid data: Date year is out of range 0000-9999 at [${pathStr(p)}]`)
215
+ }
216
+ return
217
+ }
218
+ seen.add(v)
219
+ if (Array.isArray(v)) {
220
+
221
+ //陣列以索引走訪(JSON序列化亦逐索引, 空洞為null)
222
+ for (let i = 0; i < v.length; i++) {
223
+ ks.push(i)
224
+ chkDataDate(v[i], ks, seen)
225
+ ks.pop()
226
+ }
227
+ }
228
+ else {
229
+ for (let k of Object.keys(v)) {
230
+ ks.push(k)
231
+ chkDataDate(v[k], ks, seen)
232
+ ks.pop()
233
+ }
234
+ }
235
+ }
236
+
169
237
  //isoFind, 把cvOrmFind所回傳複本內之Date就地轉為ISO字串(轉譯層): 數據以JSON存檔, Date於寫入時已成toISOString()之字串,
170
238
  //條件內之Date不轉則恆不命中; 複本僅含null原型物件、密集陣列、純量與有效Date(無共享參照、無環), 就地轉換不影響呼叫端之find
171
239
  //轉換所得之字串須為reIsoDate之形(檢查之值即使用之值), 範圍外(年份<0或>9999)者fail-closed: 拋出invalid find(帳本R-FIND-02), 不回顯值, p為路徑
@@ -342,7 +410,7 @@ function WOrmLowdb(opt = {}) {
342
410
  * 查詢數據
343
411
  *
344
412
  * 條件find僅支援系列白名單(規則同delAll之第1-3點,見delAll),與delAll共用同一檢核,select(find)命中之數據即delAll(find)刪除之數據
345
- * 條件內之Date年份須介於0000-9999,範圍外整批reject;數據側已存入之範圍外Date無法以Date條件正確比較(規則同delAll之第6點,見README)
413
+ * 條件內之Date年份須介於0000-9999,範圍外整批reject;資料庫檔內既有之範圍外Date字串(1.0.34起寫入已拒收範圍外Date)無法以Date條件正確比較(規則同delAll之第6點,見README)
346
414
  *
347
415
  * @memberOf WOrmLowdb
348
416
  * @param {Object} [find={}] 輸入查詢條件物件(系列白名單,規則同delAll之第1-3點); 未給、undefined或{}回傳全部數據; 非條件物件或白名單外運算子整批reject
@@ -469,6 +537,11 @@ function WOrmLowdb(opt = {}) {
469
537
  /**
470
538
  * 插入數據,僅於主鍵不存在時寫入,已存在者跳過且不覆寫
471
539
  *
540
+ * 數據內任何深度(頂層欄位、巢狀物件、陣列內)之Date年份須介於0000-9999(序列化為固定24字元之ISO字串,字典序即時間序),
541
+ * 範圍外(年份小於0或大於9999,如以new Date(8.64e15)作永不過期哨兵)整批reject且不寫入任何一筆,訊息為invalid data: Date year is out of range 0000-9999 at [路徑],
542
+ * 不回顯值,於reject之前發出一次error事件、不發出change事件;無效Date維持序列化為null存入;永不過期請改用null或new Date('9999-12-31T23:59:59.999Z');
543
+ * 資料庫檔內既有之範圍外Date字串不遷移,仍無法以Date條件正確比較,詳見README
544
+ *
472
545
  * @memberOf WOrmLowdb
473
546
  * @param {Object|Array} data 輸入數據物件或陣列
474
547
  * @param {Object} [option={}] 輸入設定物件,預設為{}
@@ -512,6 +585,9 @@ function WOrmLowdb(opt = {}) {
512
585
  //check id
513
586
  data = procPk(data, 'insert')
514
587
 
588
+ //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01)
589
+ chkDataDate(data)
590
+
515
591
  //read
516
592
  let arrOld = await readData()
517
593
 
@@ -604,6 +680,7 @@ function WOrmLowdb(opt = {}) {
604
680
  * 本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
605
681
  * 註: 於本套件不會較insert快,因兩者皆為一次讀檔與一次寫檔,
606
682
  * 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
683
+ * 註: 數據內任何深度之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject且不寫入任何一筆,規則同insert,詳見insert與README
607
684
  *
608
685
  * @memberOf WOrmLowdb
609
686
  * @param {Object|Array} data 輸入數據物件或陣列
@@ -636,6 +713,9 @@ function WOrmLowdb(opt = {}) {
636
713
  //check id
637
714
  data = procPk(data, 'insertBulk')
638
715
 
716
+ //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01)
717
+ chkDataDate(data)
718
+
639
719
  //read
640
720
  let arrOld = await readData()
641
721
 
@@ -704,6 +784,9 @@ function WOrmLowdb(opt = {}) {
704
784
  /**
705
785
  * 儲存數據,以主鍵為準更新既有數據,未給之欄位保留;主鍵不存在且autoInsert為true時改為插入
706
786
  *
787
+ * 數據內任何深度之Date年份須介於0000-9999,範圍外(如new Date(8.64e15))整批reject且不寫入任何一筆(逐筆失敗之ok為0機制不適用),規則同insert,詳見insert與README;
788
+ * autoInsert為false而主鍵不存在(不會寫入)者亦先經同一檢核;合併與覆寫路徑寫入之Date皆來自已檢核之數據,已存在於資料庫之字串不檢核
789
+ *
707
790
  * @memberOf WOrmLowdb
708
791
  * @param {Object|Array} data 輸入數據物件或陣列
709
792
  * @param {Object} [option={}] 輸入設定物件,預設為{}
@@ -736,6 +819,9 @@ function WOrmLowdb(opt = {}) {
736
819
  //check id
737
820
  data = procPk(data, 'save')
738
821
 
822
+ //check Date, 複本內任何深度之Date年份須介於0000-9999, 範圍外整批reject(帳本R-DATE-01); 合併與覆寫路徑之Date皆來自此複本
823
+ chkDataDate(data)
824
+
739
825
  //read
740
826
  let arrOld = await readData()
741
827
 
@@ -1078,8 +1164,9 @@ function WOrmLowdb(opt = {}) {
1078
1164
  * 欄名同於Object.prototype成員(constructor、toString等)者,數據無此自有欄位時視為缺欄;
1079
1165
  * Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦會比較):條件內任何位置之Date其年份須介於0000-9999,
1080
1166
  * 範圍外(年份小於0或大於9999,如new Date(8.64e15)、new Date(-8.64e15))整批reject,訊息為invalid find: Date year is out of range 0000-9999 at [路徑],
1081
- * 不回顯值,不讀檔、不刪除任何數據;數據側已存入之範圍外Date(如以new Date(8.64e15)作永不過期哨兵)寫入行為不變,
1082
- * 但此類資料無法以Date條件正確比較(例如{t:{$lt:現在}}會命中哨兵並將其刪除),見README。
1167
+ * 不回顯值,不讀檔、不刪除任何數據;數據側自1.0.34起insert、insertBulk與save寫入時已拒收範圍外Date,
1168
+ * 但資料庫檔內既有之範圍外Date字串(舊版以new Date(8.64e15)作永不過期哨兵存入者)仍無法以Date條件正確比較
1169
+ * (例如{t:{$lt:現在}}會命中哨兵並將其刪除),請自行清理或改寫,見README。
1083
1170
  * 需明確表達時可用可攜寫法: 排除NULL為{$and:[{f:{$ne:null}},{f:{$ne:x}}]},含NULL為{$or:[{f:null},{f:{$ne:x}}]}。
1084
1171
  *
1085
1172
  * n與nDeleted皆為實際刪除筆數,不取全表筆數。條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤。
@@ -0,0 +1,46 @@
1
+ import assert from 'assert'
2
+
3
+
4
+ //呼叫端傳入物件之逐節點快照(unit-find-whitelist與unit-date-write共用, 只定義一處)
5
+
6
+ //timeOf, 以內部槽取Date之時間值, 非Date回傳undefined(跨realm、子類別、被改Symbol.toStringTag之Date皆可辨)
7
+ let timeOf = (v) => {
8
+ try {
9
+ return Date.prototype.getTime.call(v)
10
+ }
11
+ catch (err) {
12
+ return undefined
13
+ }
14
+ }
15
+
16
+ //leaves: 取物件之全部節點(物件節點記鍵序,Date節點記毫秒,皆記參照), 供reject後確認呼叫端實際傳入之同一物件逐節點未被改動; 物件須為無環
17
+ let leaves = (v, p = 'root', out = []) => {
18
+ let ms = timeOf(v)
19
+ if (ms !== undefined) {
20
+ out.push({ p, ref: v, ms })
21
+ }
22
+ else if (v !== null && typeof v === 'object') {
23
+ out.push({ p, ref: v, keys: Object.keys(v).join(',') })
24
+ for (let k of Object.keys(v)) {
25
+ leaves(v[k], `${p}.${k}`, out)
26
+ }
27
+ }
28
+ else {
29
+ out.push({ p, ref: v })
30
+ }
31
+ return out
32
+ }
33
+
34
+ //chkSame: 兩次leaves之結果須逐節點相同(參照、毫秒、鍵序)
35
+ let chkSame = (a, b) => {
36
+ assert.strict.equal(a.length, b.length, 'object should keep the same number of nodes')
37
+ for (let i = 0; i < a.length; i++) {
38
+ assert.strict.equal(a[i].p, b[i].p)
39
+ assert.strict.equal(a[i].ref, b[i].ref, `${a[i].p} should keep the same reference or value`)
40
+ assert.strict.equal(a[i].ms, b[i].ms, `${a[i].p} should keep the same time value`)
41
+ assert.strict.equal(a[i].keys, b[i].keys, `${a[i].p} should keep the same keys`)
42
+ }
43
+ }
44
+
45
+
46
+ export { timeOf, leaves, chkSame }
@@ -0,0 +1,52 @@
1
+ //呼叫觀測器(unit-find-whitelist與unit-date-write共用, 只定義一處)
2
+
3
+ //tick, 讓出一個macrotask, 令延遲發出之事件有機會發出
4
+ let tick = () => new Promise((resolve) => setTimeout(resolve, 0))
5
+
6
+ //mkObserver, 建立呼叫觀測器: 事件記錄於evs, 每筆error事件附帶發出當下呼叫端之await是否已恢復(settledAtEmit, 即呼叫端可觀察之先後)
7
+ let mkObserver = (wo) => {
8
+ let evs = []
9
+ let cur = { settled: false }
10
+ wo.on('error', function(mode, data, err) {
11
+ evs.push({ t: 'error', mode, data, err, settledAtEmit: cur.settled })
12
+ })
13
+ wo.on('change', function(mode) {
14
+ evs.push({ t: 'change', mode })
15
+ })
16
+
17
+ //run, 執行call()(回傳Promise之函數)並回傳{ ok, val, err, nAtSettle, probe, evs }, nAtSettle為結果定案當下之事件數,
18
+ //probe為結果定案當下(同一microtask鏈內, 尚未讓出macrotask)呼叫probeFn()之回傳, evs為再讓出一個macrotask後之事件
19
+ let run = async(call, probeFn = null) => {
20
+ evs.length = 0
21
+ cur = { settled: false }
22
+ let out = { ok: false, val: null, err: null, nAtSettle: 0, probe: null, evs: [] }
23
+ try {
24
+ out.val = await call()
25
+ out.ok = true
26
+ }
27
+ catch (err) {
28
+ out.err = err
29
+ }
30
+ cur.settled = true
31
+ out.nAtSettle = evs.length
32
+ out.probe = probeFn ? probeFn() : null
33
+ await tick()
34
+ out.evs = evs.slice()
35
+ return out
36
+ }
37
+
38
+ //invoke, 呼叫wo[fn](arg)
39
+ let invoke = (fn, arg, probeFn = null) => {
40
+ return run(() => wo[fn](arg), probeFn)
41
+ }
42
+
43
+ //clear
44
+ let clear = () => {
45
+ evs.length = 0
46
+ }
47
+
48
+ return { run, invoke, clear }
49
+ }
50
+
51
+
52
+ export { tick, mkObserver }