w-serv-orm 1.1.10 → 1.1.11

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.
@@ -78,15 +78,19 @@
78
78
  <p>每次呼叫各自建立資料表ORM、設定與擴充函數,同一行程內之多個實例互不干擾;同一url與db之資料庫資源能否並存,依所用ORM而定。</p>
79
79
  <p>procOrm之save、del與mix,於ORM回傳之逐筆結果有任一筆ok為0(單筆失敗)時reject,訊息列出失敗筆之主鍵,如'failed to save data: id[b]'(超過100字元者截斷顯示;無有效主鍵者以data[k]列其於輸入內之位置,k自0起;超過10筆者只列前10筆並附總筆數),錯誤原文另由console.log輸出。
80
80
  單筆失敗不中斷整批,故reject時其餘筆數已寫入。
81
- mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。</p>
82
- <p>insert、save與mix之新增、修改,數據未帶有效主鍵(非空字串或數字)者,若資料表之主鍵欄為id且為字串型(或型別未知),由本套件產生主鍵(同各ORM之genIDSeq)並於resolve時回傳;無欄位之數據不補。
83
- reject時不回傳補出之主鍵,故重送未帶主鍵之數據會再產生新主鍵而重複寫入,需冪等重送者請自帶主鍵;save帶主鍵者重送以主鍵覆寫,del重送時已刪除者為主鍵未命中。
81
+ mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。mix於resolve時回傳各步驟所處理之列之id(依add、del、diff之序串接,del之列不分是否命中)。</p>
82
+ <p>insert、save與mix之新增、修改,數據未帶有效主鍵(未給、null、空字串,或非字串非數字之值,含NaN與±Infinity)者,若資料表之主鍵欄為id且為字串型(或型別未知;型別如STRING(36)者以括號前之字判定),由本套件產生主鍵(同各ORM之genIDSeq)並於resolve時回傳;單一空物件與只含一個空物件之陣列不補(ORM視為無效輸入而不寫入),多筆批次中之空物件列則補。
83
+ 主鍵欄為字串型(或型別未知)之資料表,主鍵為有限數字者一律以其字串形式處置(如123視為'123'):寫入、resolve回傳、del與mix之刪除,以及select、delAll條件中主鍵之比較($eq、$ne、$gt、$gte、$lt、$lte、$in、$nin、$all,含$not、$and、$or、$nor之內)皆同,故以數字或其字串形式寫入、查詢與刪除者為同一筆,範圍比較依字串之字典序;主鍵欄為整數型者不轉型。轉型只作用於procOrm,經woItems直接操作者不轉型。舊版w-orm-mongodb(2025-04-29以前)保留之數字主鍵資料,以數字查詢將查不到。
84
+ reject時不回傳補出之主鍵,故重送未帶主鍵之數據會再產生新主鍵而重複寫入,需冪等重送者請自帶主鍵(非空字串或有限數字);save帶主鍵者重送以主鍵覆寫,del重送時已刪除者為主鍵未命中。
85
+ 回傳之id為本套件依輸入(含補值與轉型)所得之主鍵,非ORM之寫入結果;ORM之比對規則較寬者(如w-orm-mdb之Jet不分大小寫、忽略尾端空白),insert略過或save命中之對象可能與回傳之id不同。lmdb、lowdb、level、mongodb等主鍵固定為字串id之ORM,其資料表之id須為字串型pk或不標型別;本套件以id為主鍵,主鍵欄非id之ORM(如w-orm-postgresql預設為time)之回傳id不具主鍵意義。
86
+ 輸入之形狀:insert與save之各筆、mix之新增與修改之各筆須為物件,mix之add、del、diff若給予須為陣列,delAll之條件若給予須為物件,否則於任何寫入之前reject,如'failed to insert data, not an object: data[0]'、'invalid find of delAll, need an object'。
87
+ mix之add與del、del與diff含同一主鍵(依上述轉型後)者,於任何步驟之前整批reject且不寫入任何一筆,訊息列其位置而不回顯主鍵,如'invalid input of mix, same id in add and del: add[0], del[1]';因mix依add、del、diff之序執行,同鍵將使新增因已存在而略過後即被刪除。
84
88
  insert(含mix之新增)為主鍵已存在即略過之冪等新增,回傳之id含被略過者;recover亦以insert還原,故資料表須先清空。
85
89
  insert、save與mix之新增、修改,寫入前各欄位之字串皆經wsemi之clearXSS清除(白名單外之標籤轉義,白名單內之標籤刪除白名單外之屬性與危險網址,標籤外之文字只轉義會開始標籤之&lt;,故'a &lt; b'等數學式不改寫);select與del不經清除。
86
90
  字串主鍵若會被清除改寫(含HTML標記,如&lt;後緊接英文字母),整批reject且不寫入任何一筆(mix於任何步驟之前檢查新增與修改),訊息列其位置而不回顯主鍵,如'failed to save data, id contains HTML markup: data[1]';否則改寫後之主鍵與呼叫端所持者不同,以原主鍵查詢與刪除皆找不到。</p>
87
91
  <p>擴充函數(addFunCheck、addFunPreProcessing、addFunPostProcessing)屬於各實例,重建實例後須重新註冊;註冊時須傳入函數,否則立即拋錯(addFunCheck另令該實例之procOrm一律reject);註冊後約1毫秒(以計時器)才生效,在此之前之procOrm呼叫不會套用。
88
92
  呼叫契約:check以reject或拋錯阻擋,回傳值不影響結果,且早於前處理執行;前處理逐一呼叫,各收新建之參數物件{woName,userId,mode,input},input為前一個回傳之input,回傳須為含input之物件否則reject;後處理逐一呼叫,各收新建之參數物件且其output為前一個之回傳值,最後之回傳值即為procOrm之回傳值。
89
- 後處理只在check、前處理與ORM操作皆成功後執行,逐筆失敗(含del)或mix部分寫入後reject者皆不執行;後處理自身亦可reject。後處理之input為呼叫端傳入之數據,不含本套件補出之主鍵,主鍵見output。
93
+ 後處理只在check、前處理與ORM操作皆成功後執行,逐筆失敗(含del)或mix部分寫入後reject者皆不執行;後處理自身亦可reject。後處理之input為前處理後之數據(無前處理者即呼叫端傳入之數據),不含本套件之補值與主鍵轉型,主鍵見output。
90
94
  如需觀察ORM實際寫入,可另監聽woItems內各ORM之change與error事件;惟事件由ORM發出,不含userId,mix會依步驟分次發出,經woItems直接寫入者亦會發出,insert之結果為聚合計數而非逐筆。
91
95
  錯誤型別:procOrm由本套件產生之reject一律為英文訊息字串(供前端顯示或對應多語系;ORM之錯誤原文另由console.log輸出而不轉出),擴充函數自身之reject或拋錯則原樣轉出;建構(設定或ORM無效)、擴充函數註冊、backup與recover由本套件產生之錯誤一律為Error,ORM之錯誤則原樣轉出。</p>
92
96
  <p>授權:procOrm不做授權,不檢查使用者,亦不依使用者過濾數據;userId只供寫入稽核欄位(userId、userIdUpdate)與交給擴充函數。procOrm開放給前端時(如讓前端經資料通道直接操作資料表),須由開放處負責授權;addFunCheck可掛額外檢查,惟伺服端程式之呼叫亦會經過。
@@ -96,7 +100,7 @@ insert、save與mix之新增、修改,寫入前各欄位之字串皆經wsemi
96
100
 
97
101
  <dt class="tag-source">Source:</dt>
98
102
  <dd class="tag-source"><ul class="dummy"><li>
99
- <a href="WServOrm.mjs.html">WServOrm.mjs</a>, <a href="WServOrm.mjs.html#line96">line 96</a>
103
+ <a href="WServOrm.mjs.html">WServOrm.mjs</a>, <a href="WServOrm.mjs.html#line100">line 100</a>
100
104
  </li></ul></dd>
101
105
 
102
106
 
@@ -439,7 +443,7 @@ insert、save與mix之新增、修改,寫入前各欄位之字串皆經wsemi
439
443
  <br class="clear">
440
444
 
441
445
  <footer>
442
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Oct 13 2026 06:34:48 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
446
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Oct 14 2026 07:49:10 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
443
447
  </footer>
444
448
 
445
449
  <script>prettyPrint();</script>
@@ -61,17 +61,21 @@ import getProc from './getProc.mjs'
61
61
  *
62
62
  * procOrm之save、del與mix,於ORM回傳之逐筆結果有任一筆ok為0(單筆失敗)時reject,訊息列出失敗筆之主鍵,如'failed to save data: id[b]'(超過100字元者截斷顯示;無有效主鍵者以data[k]列其於輸入內之位置,k自0起;超過10筆者只列前10筆並附總筆數),錯誤原文另由console.log輸出。
63
63
  * 單筆失敗不中斷整批,故reject時其餘筆數已寫入。
64
- * mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。
64
+ * mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。mix於resolve時回傳各步驟所處理之列之id(依add、del、diff之序串接,del之列不分是否命中)。
65
65
  *
66
- * insert、save與mix之新增、修改,數據未帶有效主鍵(非空字串或數字)者,若資料表之主鍵欄為id且為字串型(或型別未知),由本套件產生主鍵(同各ORM之genIDSeq)並於resolve時回傳;無欄位之數據不補。
67
- * reject時不回傳補出之主鍵,故重送未帶主鍵之數據會再產生新主鍵而重複寫入,需冪等重送者請自帶主鍵;save帶主鍵者重送以主鍵覆寫,del重送時已刪除者為主鍵未命中。
66
+ * insert、save與mix之新增、修改,數據未帶有效主鍵(未給、null、空字串,或非字串非數字之值,含NaN與±Infinity)者,若資料表之主鍵欄為id且為字串型(或型別未知;型別如STRING(36)者以括號前之字判定),由本套件產生主鍵(同各ORM之genIDSeq)並於resolve時回傳;單一空物件與只含一個空物件之陣列不補(ORM視為無效輸入而不寫入),多筆批次中之空物件列則補。
67
+ * 主鍵欄為字串型(或型別未知)之資料表,主鍵為有限數字者一律以其字串形式處置(如123視為'123'):寫入、resolve回傳、del與mix之刪除,以及select、delAll條件中主鍵之比較($eq、$ne、$gt、$gte、$lt、$lte、$in、$nin、$all,含$not、$and、$or、$nor之內)皆同,故以數字或其字串形式寫入、查詢與刪除者為同一筆,範圍比較依字串之字典序;主鍵欄為整數型者不轉型。轉型只作用於procOrm,經woItems直接操作者不轉型。舊版w-orm-mongodb(2025-04-29以前)保留之數字主鍵資料,以數字查詢將查不到。
68
+ * reject時不回傳補出之主鍵,故重送未帶主鍵之數據會再產生新主鍵而重複寫入,需冪等重送者請自帶主鍵(非空字串或有限數字);save帶主鍵者重送以主鍵覆寫,del重送時已刪除者為主鍵未命中。
69
+ * 回傳之id為本套件依輸入(含補值與轉型)所得之主鍵,非ORM之寫入結果;ORM之比對規則較寬者(如w-orm-mdb之Jet不分大小寫、忽略尾端空白),insert略過或save命中之對象可能與回傳之id不同。lmdb、lowdb、level、mongodb等主鍵固定為字串id之ORM,其資料表之id須為字串型pk或不標型別;本套件以id為主鍵,主鍵欄非id之ORM(如w-orm-postgresql預設為time)之回傳id不具主鍵意義。
70
+ * 輸入之形狀:insert與save之各筆、mix之新增與修改之各筆須為物件,mix之add、del、diff若給予須為陣列,delAll之條件若給予須為物件,否則於任何寫入之前reject,如'failed to insert data, not an object: data[0]'、'invalid find of delAll, need an object'。
71
+ * mix之add與del、del與diff含同一主鍵(依上述轉型後)者,於任何步驟之前整批reject且不寫入任何一筆,訊息列其位置而不回顯主鍵,如'invalid input of mix, same id in add and del: add[0], del[1]';因mix依add、del、diff之序執行,同鍵將使新增因已存在而略過後即被刪除。
68
72
  * insert(含mix之新增)為主鍵已存在即略過之冪等新增,回傳之id含被略過者;recover亦以insert還原,故資料表須先清空。
69
73
  * insert、save與mix之新增、修改,寫入前各欄位之字串皆經wsemi之clearXSS清除(白名單外之標籤轉義,白名單內之標籤刪除白名單外之屬性與危險網址,標籤外之文字只轉義會開始標籤之&lt;,故'a &lt; b'等數學式不改寫);select與del不經清除。
70
74
  * 字串主鍵若會被清除改寫(含HTML標記,如&lt;後緊接英文字母),整批reject且不寫入任何一筆(mix於任何步驟之前檢查新增與修改),訊息列其位置而不回顯主鍵,如'failed to save data, id contains HTML markup: data[1]';否則改寫後之主鍵與呼叫端所持者不同,以原主鍵查詢與刪除皆找不到。
71
75
  *
72
76
  * 擴充函數(addFunCheck、addFunPreProcessing、addFunPostProcessing)屬於各實例,重建實例後須重新註冊;註冊時須傳入函數,否則立即拋錯(addFunCheck另令該實例之procOrm一律reject);註冊後約1毫秒(以計時器)才生效,在此之前之procOrm呼叫不會套用。
73
77
  * 呼叫契約:check以reject或拋錯阻擋,回傳值不影響結果,且早於前處理執行;前處理逐一呼叫,各收新建之參數物件{woName,userId,mode,input},input為前一個回傳之input,回傳須為含input之物件否則reject;後處理逐一呼叫,各收新建之參數物件且其output為前一個之回傳值,最後之回傳值即為procOrm之回傳值。
74
- * 後處理只在check、前處理與ORM操作皆成功後執行,逐筆失敗(含del)或mix部分寫入後reject者皆不執行;後處理自身亦可reject。後處理之input為呼叫端傳入之數據,不含本套件補出之主鍵,主鍵見output。
78
+ * 後處理只在check、前處理與ORM操作皆成功後執行,逐筆失敗(含del)或mix部分寫入後reject者皆不執行;後處理自身亦可reject。後處理之input為前處理後之數據(無前處理者即呼叫端傳入之數據),不含本套件之補值與主鍵轉型,主鍵見output。
75
79
  * 如需觀察ORM實際寫入,可另監聽woItems內各ORM之change與error事件;惟事件由ORM發出,不含userId,mix會依步驟分次發出,經woItems直接寫入者亦會發出,insert之結果為聚合計數而非逐筆。
76
80
  * 錯誤型別:procOrm由本套件產生之reject一律為英文訊息字串(供前端顯示或對應多語系;ORM之錯誤原文另由console.log輸出而不轉出),擴充函數自身之reject或拋錯則原樣轉出;建構(設定或ORM無效)、擴充函數註冊、backup與recover由本套件產生之錯誤一律為Error,ORM之錯誤則原樣轉出。
77
81
  *
@@ -191,7 +195,7 @@ export default WServOrm
191
195
  <br class="clear">
192
196
 
193
197
  <footer>
194
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Oct 13 2026 06:34:48 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
198
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Oct 14 2026 07:49:10 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
195
199
  </footer>
196
200
 
197
201
  <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 13 2026 06:34:48 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 14 2026 07:49:10 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,15 +1,15 @@
1
1
  {
2
2
  "name": "w-serv-orm",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "main": "dist/w-serv-orm.umd.js",
5
5
  "dependencies": {
6
6
  "json5": "^2.2.3",
7
7
  "w-data-collector": "^1.0.46",
8
- "wsemi": "^1.9.13"
8
+ "wsemi": "^1.9.15"
9
9
  },
10
10
  "devDependencies": {
11
11
  "w-orm-lowdb": "^1.0.31",
12
- "w-package-tools": "^1.1.16"
12
+ "w-package-tools": "^1.1.18"
13
13
  },
14
14
  "scripts": {
15
15
  "test": "mocha --parallel \"test/*.test.mjs\" --timeout 180000",
package/src/WServOrm.mjs CHANGED
@@ -14,17 +14,21 @@ import getProc from './getProc.mjs'
14
14
  *
15
15
  * procOrm之save、del與mix,於ORM回傳之逐筆結果有任一筆ok為0(單筆失敗)時reject,訊息列出失敗筆之主鍵,如'failed to save data: id[b]'(超過100字元者截斷顯示;無有效主鍵者以data[k]列其於輸入內之位置,k自0起;超過10筆者只列前10筆並附總筆數),錯誤原文另由console.log輸出。
16
16
  * 單筆失敗不中斷整批,故reject時其餘筆數已寫入。
17
- * mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。
17
+ * mix之各步驟遇單筆失敗不中斷後續步驟,待全部步驟完成後一併reject;遇整批性錯誤則立即reject,後續步驟不執行。mix於resolve時回傳各步驟所處理之列之id(依add、del、diff之序串接,del之列不分是否命中)。
18
18
  *
19
- * insert、save與mix之新增、修改,數據未帶有效主鍵(非空字串或數字)者,若資料表之主鍵欄為id且為字串型(或型別未知),由本套件產生主鍵(同各ORM之genIDSeq)並於resolve時回傳;無欄位之數據不補。
20
- * reject時不回傳補出之主鍵,故重送未帶主鍵之數據會再產生新主鍵而重複寫入,需冪等重送者請自帶主鍵;save帶主鍵者重送以主鍵覆寫,del重送時已刪除者為主鍵未命中。
19
+ * insert、save與mix之新增、修改,數據未帶有效主鍵(未給、null、空字串,或非字串非數字之值,含NaN與±Infinity)者,若資料表之主鍵欄為id且為字串型(或型別未知;型別如STRING(36)者以括號前之字判定),由本套件產生主鍵(同各ORM之genIDSeq)並於resolve時回傳;單一空物件與只含一個空物件之陣列不補(ORM視為無效輸入而不寫入),多筆批次中之空物件列則補。
20
+ * 主鍵欄為字串型(或型別未知)之資料表,主鍵為有限數字者一律以其字串形式處置(如123視為'123'):寫入、resolve回傳、del與mix之刪除,以及select、delAll條件中主鍵之比較($eq、$ne、$gt、$gte、$lt、$lte、$in、$nin、$all,含$not、$and、$or、$nor之內)皆同,故以數字或其字串形式寫入、查詢與刪除者為同一筆,範圍比較依字串之字典序;主鍵欄為整數型者不轉型。轉型只作用於procOrm,經woItems直接操作者不轉型。舊版w-orm-mongodb(2025-04-29以前)保留之數字主鍵資料,以數字查詢將查不到。
21
+ * reject時不回傳補出之主鍵,故重送未帶主鍵之數據會再產生新主鍵而重複寫入,需冪等重送者請自帶主鍵(非空字串或有限數字);save帶主鍵者重送以主鍵覆寫,del重送時已刪除者為主鍵未命中。
22
+ * 回傳之id為本套件依輸入(含補值與轉型)所得之主鍵,非ORM之寫入結果;ORM之比對規則較寬者(如w-orm-mdb之Jet不分大小寫、忽略尾端空白),insert略過或save命中之對象可能與回傳之id不同。lmdb、lowdb、level、mongodb等主鍵固定為字串id之ORM,其資料表之id須為字串型pk或不標型別;本套件以id為主鍵,主鍵欄非id之ORM(如w-orm-postgresql預設為time)之回傳id不具主鍵意義。
23
+ * 輸入之形狀:insert與save之各筆、mix之新增與修改之各筆須為物件,mix之add、del、diff若給予須為陣列,delAll之條件若給予須為物件,否則於任何寫入之前reject,如'failed to insert data, not an object: data[0]'、'invalid find of delAll, need an object'。
24
+ * mix之add與del、del與diff含同一主鍵(依上述轉型後)者,於任何步驟之前整批reject且不寫入任何一筆,訊息列其位置而不回顯主鍵,如'invalid input of mix, same id in add and del: add[0], del[1]';因mix依add、del、diff之序執行,同鍵將使新增因已存在而略過後即被刪除。
21
25
  * insert(含mix之新增)為主鍵已存在即略過之冪等新增,回傳之id含被略過者;recover亦以insert還原,故資料表須先清空。
22
26
  * insert、save與mix之新增、修改,寫入前各欄位之字串皆經wsemi之clearXSS清除(白名單外之標籤轉義,白名單內之標籤刪除白名單外之屬性與危險網址,標籤外之文字只轉義會開始標籤之<,故'a < b'等數學式不改寫);select與del不經清除。
23
27
  * 字串主鍵若會被清除改寫(含HTML標記,如<後緊接英文字母),整批reject且不寫入任何一筆(mix於任何步驟之前檢查新增與修改),訊息列其位置而不回顯主鍵,如'failed to save data, id contains HTML markup: data[1]';否則改寫後之主鍵與呼叫端所持者不同,以原主鍵查詢與刪除皆找不到。
24
28
  *
25
29
  * 擴充函數(addFunCheck、addFunPreProcessing、addFunPostProcessing)屬於各實例,重建實例後須重新註冊;註冊時須傳入函數,否則立即拋錯(addFunCheck另令該實例之procOrm一律reject);註冊後約1毫秒(以計時器)才生效,在此之前之procOrm呼叫不會套用。
26
30
  * 呼叫契約:check以reject或拋錯阻擋,回傳值不影響結果,且早於前處理執行;前處理逐一呼叫,各收新建之參數物件{woName,userId,mode,input},input為前一個回傳之input,回傳須為含input之物件否則reject;後處理逐一呼叫,各收新建之參數物件且其output為前一個之回傳值,最後之回傳值即為procOrm之回傳值。
27
- * 後處理只在check、前處理與ORM操作皆成功後執行,逐筆失敗(含del)或mix部分寫入後reject者皆不執行;後處理自身亦可reject。後處理之input為呼叫端傳入之數據,不含本套件補出之主鍵,主鍵見output。
31
+ * 後處理只在check、前處理與ORM操作皆成功後執行,逐筆失敗(含del)或mix部分寫入後reject者皆不執行;後處理自身亦可reject。後處理之input為前處理後之數據(無前處理者即呼叫端傳入之數據),不含本套件之補值與主鍵轉型,主鍵見output。
28
32
  * 如需觀察ORM實際寫入,可另監聽woItems內各ORM之change與error事件;惟事件由ORM發出,不含userId,mix會依步驟分次發出,經woItems直接寫入者亦會發出,insert之結果為聚合計數而非逐筆。
29
33
  * 錯誤型別:procOrm由本套件產生之reject一律為英文訊息字串(供前端顯示或對應多語系;ORM之錯誤原文另由console.log輸出而不轉出),擴充函數自身之reject或拋錯則原樣轉出;建構(設定或ORM無效)、擴充函數註冊、backup與recover由本套件產生之錯誤一律為Error,ORM之錯誤則原樣轉出。
30
34
  *
package/src/getMapOrm.mjs CHANGED
@@ -8,6 +8,8 @@ import truncate from 'lodash-es/truncate.js'
8
8
  import nowms2str from 'wsemi/src/nowms2str.mjs'
9
9
  import genIDSeq from 'wsemi/src/genIDSeq.mjs'
10
10
  import isarr from 'wsemi/src/isarr.mjs'
11
+ import isearr from 'wsemi/src/isearr.mjs'
12
+ import isobj from 'wsemi/src/isobj.mjs'
11
13
  import iseobj from 'wsemi/src/iseobj.mjs'
12
14
  import isestr from 'wsemi/src/isestr.mjs'
13
15
  import isnum from 'wsemi/src/isnum.mjs'
@@ -18,6 +20,7 @@ import getItemsFail from 'w-data-collector/src/getItemsFail.mjs'
18
20
  function getMsgFail(msg, fails) {
19
21
  //前端訊息僅列出失敗筆, 錯誤原文另由console.log輸出, 與既有整批錯誤之處理一致
20
22
  //有效主鍵(非空字串或數字)列為id[...], 否則列其於輸入內之位置, 失敗筆數過多時僅列前nShow筆並附總筆數
23
+ //呼叫端給予位置標籤ref者(如mix同鍵之add[0])原樣列出
21
24
  //主鍵超過nIdMax字元者截斷顯示(LMDB逐筆失敗常因主鍵超長, 故兩者常同時出現), log仍保留全文
22
25
 
23
26
  //nShow, nIdMax
@@ -26,6 +29,9 @@ function getMsgFail(msg, fails) {
26
29
 
27
30
  //ss
28
31
  let ss = map(take(fails, nShow), (v) => {
32
+ if (isestr(v.ref)) {
33
+ return v.ref
34
+ }
29
35
  if (isestr(v.id) || isnum(v.id)) {
30
36
  return `id[${truncate(String(v.id), { length: nIdMax, omission: '…' })}]`
31
37
  }
@@ -55,8 +61,9 @@ function isPkIdString(dsTab, keys) {
55
61
  }
56
62
 
57
63
  //check, 型別已知者須為字串型, 與w-orm-reladb可自動補值之主鍵欄型別一致
64
+ //比對前取「(」之前之字並轉大寫, 如STRING(36)視為STRING, 同w-orm-reladb以type.key判定而不含長度參數
58
65
  let type = get(settings, 'id.type', '') || get(dsTab, 'kpType.id', '')
59
- if (isestr(type) && ['STRING', 'TEXT', 'CHAR', 'UUID', 'CITEXT'].indexOf(type.toUpperCase()) < 0) {
66
+ if (isestr(type) && ['STRING', 'TEXT', 'CHAR', 'UUID', 'CITEXT'].indexOf(type.split('(')[0].trim().toUpperCase()) < 0) {
60
67
  return false
61
68
  }
62
69
 
@@ -64,6 +71,147 @@ function isPkIdString(dsTab, keys) {
64
71
  }
65
72
 
66
73
 
74
+ function isIdCast(ds, woName) {
75
+ //資料表之主鍵欄為字串型或型別未知者, 主鍵之有限數字以其字串形式處置(R-ID-02); ds無該表者不轉型, 令select與del照舊原樣交ORM
76
+ let dsTab = get(ds, woName, null)
77
+ let keys = get(dsTab, 'keys', null)
78
+ if (!isarr(keys)) {
79
+ return false
80
+ }
81
+ return isPkIdString(dsTab, keys)
82
+ }
83
+
84
+
85
+ function isIdNumFinite(v) {
86
+ //主鍵值是否為有限數字, 不用wsemi之isnum: 其對數字字串與Infinity亦為true
87
+ return typeof v === 'number' && Number.isFinite(v)
88
+ }
89
+
90
+
91
+ function castIdValue(v) {
92
+ //主鍵值之正規化: 有限數字轉為其字串形式, 其餘原樣(NaN、±Infinity亦原樣, 寫入時由adjustData視為未提供而補值)
93
+ //各後端之主鍵判準互異, lmdb、lowdb、level、mongodb只認非空字串而以genIDSeq取代數字, reladb、mdb之字串欄由資料庫把數字存為字串,
94
+ //故由本套件先轉, 令寫入、刪除與查詢條件同一規則, 回傳之主鍵與實際寫入者一致
95
+ return isIdNumFinite(v) ? String(v) : v
96
+ }
97
+
98
+
99
+ function castIdRows(rows) {
100
+ //寫入與刪除之數據: 各列之主鍵正規化, 寫於複本, 不改呼叫端之物件; 非物件之列原樣
101
+ let one = !isarr(rows)
102
+ let rs = map(one ? [rows] : rows, (v) => {
103
+ if (isobj(v) && isIdNumFinite(v.id)) {
104
+ return { ...v, id: castIdValue(v.id) }
105
+ }
106
+ return v
107
+ })
108
+ return one ? rs[0] : rs
109
+ }
110
+
111
+
112
+ function castIdCond(c) {
113
+ //主鍵之條件: 值或運算子物件; 比較運算子之值(陣列逐一)轉型, $not遞迴, 其餘運算子($regex、$exists等)原樣
114
+ if (!isobj(c)) {
115
+ return castIdValue(c)
116
+ }
117
+ let r = {}
118
+ each(c, (v, k) => {
119
+ if (k === '$not') {
120
+ r[k] = castIdCond(v)
121
+ }
122
+ else if (['$eq', '$ne', '$gt', '$gte', '$lt', '$lte', '$in', '$nin', '$all'].indexOf(k) >= 0) {
123
+ r[k] = isarr(v) ? map(v, castIdValue) : castIdValue(v)
124
+ }
125
+ else {
126
+ r[k] = v
127
+ }
128
+ })
129
+ return r
130
+ }
131
+
132
+
133
+ function castIdFind(find) {
134
+ //查詢條件(select、delAll之find): id之條件轉型, 遞迴$and、$or、$nor; 他欄原樣; 寫於複本, 非物件原樣
135
+ if (!isobj(find)) {
136
+ return find
137
+ }
138
+ let r = {}
139
+ each(find, (v, k) => {
140
+ if (k === 'id') {
141
+ r[k] = castIdCond(v)
142
+ }
143
+ else if (['$and', '$or', '$nor'].indexOf(k) >= 0 && isarr(v)) {
144
+ r[k] = map(v, castIdFind)
145
+ }
146
+ else {
147
+ r[k] = v
148
+ }
149
+ })
150
+ return r
151
+ }
152
+
153
+
154
+ function getItemsNotObj(data) {
155
+ //寫入之各筆須為一般物件(R-INPUT-01), 回傳[{k}], k為其於輸入內之位置; 單一非物件輸入視為第0筆
156
+ //非物件之元素經adjustData之clearXSS逐鍵複製時, 字串、陣列被展開成{0:...,1:...}、數字等變成{}, 寫入垃圾紀錄
157
+ let rs = []
158
+ each(isarr(data) ? data : [data], (v, k) => {
159
+ if (!isobj(v)) {
160
+ rs.push({ k })
161
+ }
162
+ })
163
+ return rs
164
+ }
165
+
166
+
167
+ function getMixIdKey(v) {
168
+ //mix跨段比對用之主鍵鍵值(R-MIX-01): 以castIdValue正規化(有限數字取其字串形式, 與寫入、刪除之轉型同一函數), 正規化後為非空字串者取之, 其餘(未提供)不參與比對
169
+ //不論資料表是否適用轉型(R-ID-02)皆正規化, 故整數主鍵欄之5與'5'亦視為同鍵
170
+ let id = castIdValue(get(v, 'id'))
171
+ return isestr(id) ? id : null
172
+ }
173
+
174
+
175
+ function getMsgsMixSameId(input) {
176
+ //mix之add與del、del與diff含同一主鍵者回傳各組之訊息(R-MIX-01), 位置以段名[k]列出而不回顯主鍵
177
+ //mix依add、del、diff之序執行, add與del同鍵將使新增因已存在而略過後即被刪除, del與diff同鍵將使刪除後由save以部份欄位重新插入
178
+ let msgs = []
179
+ for (let [ka, kb] of [['add', 'del'], ['del', 'diff']]) {
180
+
181
+ //ma, 前段各主鍵之位置
182
+ let ma = new Map()
183
+ each(get(input, ka, []), (v, k) => {
184
+ let key = getMixIdKey(v)
185
+ if (key !== null) {
186
+ ma.set(key, [...(ma.get(key) || []), k])
187
+ }
188
+ })
189
+
190
+ //ia, ib, 同鍵者之位置
191
+ let ia = []
192
+ let ib = []
193
+ each(get(input, kb, []), (v, k) => {
194
+ let key = getMixIdKey(v)
195
+ if (key !== null && ma.has(key)) {
196
+ ia = [...ia, ...ma.get(key)]
197
+ ib.push(k)
198
+ }
199
+ })
200
+
201
+ //msgs
202
+ if (size(ib) > 0) {
203
+ let refs = [
204
+ ...map([...new Set(ia)].sort((a, b) => a - b), (k) => ({ ref: `${ka}[${k}]` })),
205
+ ...map(ib, (k) => ({ ref: `${kb}[${k}]` })),
206
+ ]
207
+ msgs.push(getMsgFail(`invalid input of mix, same id in ${ka} and ${kb}`, refs))
208
+ }
209
+
210
+ }
211
+ return msgs
212
+ }
213
+
214
+
67
215
  function getItemsIdXSS(data) {
68
216
  //輸入數據之主鍵(非空字串)會被clearXSS改寫者, 回傳[{k,id}], k為其於輸入內之位置
69
217
  //寫入路徑之欄位皆經clearXSS而select與del不經過, 主鍵被改寫後以原主鍵查詢與刪除皆找不到, 故呼叫端須於寫入前整批拒絕
@@ -172,15 +320,20 @@ function adjustData(ds, woName, userId, data, mode = 'save') { //mode: 'insert',
172
320
  })
173
321
  }
174
322
 
175
- //id, 未提供有效主鍵者由本套件補值, 令回傳之主鍵與實際寫入者一致, 補值器同各ORM之genIDSeq
176
- //有效主鍵之判定同w-orm系列: 非空字串或數字(reladb、mdb視數字為有效)
177
- //補值寫在上方之複本, 不改呼叫端原物件; 無欄位之列不補, 保留ORM視空物件為無效輸入之行為
323
+ //id, 未提供有效主鍵者由本套件補值, 令回傳之主鍵與實際寫入者一致, 補值器同各ORM之genIDSeq(R-ID-01)
324
+ //有效主鍵為非空字串: 字串型主鍵欄之有限數字已於mapOrm轉為字串(R-ID-02), 剩餘之數字(NaN、±Infinity)與其他型別視為未提供
325
+ //補值寫在上方之複本, 不改呼叫端原物件
326
+ //輸入依wsemi之iseobj、isearr判為無效者(單一空物件、只含一個空物件之陣列)不補, 保留ORM視之為無效輸入(規格T5)而不寫入之行為;
327
+ //多筆批次中之空物件列則補, 因後端照常補值寫入, 不補則回傳undefined而與實際不符
178
328
  if (isPkIdString(ds[woName], keys)) {
179
- each(data, (v) => {
180
- if (size(v) > 0 && !isestr(v.id) && !isnum(v.id)) {
181
- v.id = genIDSeq()
182
- }
183
- })
329
+ let effective = one ? iseobj(data[0]) : isearr(data)
330
+ if (effective) {
331
+ each(data, (v) => {
332
+ if (!isestr(v.id)) {
333
+ v.id = genIDSeq()
334
+ }
335
+ })
336
+ }
184
337
  }
185
338
 
186
339
  //復原data型態, 若原本data為物件則取第0個回傳
@@ -208,10 +361,13 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
208
361
  return Promise.reject(`can not find table[${woName}] in ORM`)
209
362
  }
210
363
 
364
+ //idCast, 主鍵之有限數字以其字串形式處置(R-ID-02), 寫入、刪除與查詢條件共用同一判定
365
+ let idCast = isIdCast(ds, woName)
366
+
211
367
  if (mode === 'select') {
212
368
 
213
- //select
214
- r = await wo.select(input)
369
+ //select, 條件中主鍵之比較值轉型(R-ID-02)
370
+ r = await wo.select(idCast ? castIdFind(input) : input)
215
371
  .catch((msg) => {
216
372
  console.log(`${woName}.select catch`, msg)
217
373
  err = 'failed to select data'
@@ -228,11 +384,26 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
228
384
  //inputOrig, 呼叫端原輸入, 逐筆失敗之訊息以呼叫端視角列出(本套件補出之主鍵只寫於複本, reject時呼叫端未曾取得)
229
385
  let inputOrig = input
230
386
 
387
+ //msgMode
388
+ let msgMode = mode === 'insert' ? 'failed to insert data' : 'failed to save data'
389
+
390
+ //check, 各筆須為一般物件, 否則整批拒絕且不寫入任何一筆(R-INPUT-01)
391
+ let failsNotObj = getItemsNotObj(input)
392
+ if (size(failsNotObj) > 0) {
393
+ console.log(`${woName}.${mode} data is not an object`, failsNotObj)
394
+ return Promise.reject(getMsgFail(`${msgMode}, not an object`, failsNotObj))
395
+ }
396
+
397
+ //castIdRows, 主鍵之有限數字轉為字串, 寫於複本(R-ID-02)
398
+ if (idCast) {
399
+ input = castIdRows(input)
400
+ }
401
+
231
402
  //check, 主鍵會被clearXSS改寫者整批拒絕, 不寫入任何一筆
232
403
  let failsIdXSS = getItemsIdXSS(input)
233
404
  if (size(failsIdXSS) > 0) {
234
405
  console.log(`${woName}.${mode} id changed by clearXSS`, failsIdXSS)
235
- return Promise.reject(getMsgIdXSS(mode === 'insert' ? 'failed to insert data' : 'failed to save data', failsIdXSS))
406
+ return Promise.reject(getMsgIdXSS(msgMode, failsIdXSS))
236
407
  }
237
408
 
238
409
  //adjustData
@@ -290,6 +461,11 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
290
461
  }
291
462
  else if (mode === 'del') {
292
463
 
464
+ //castIdRows, 主鍵之有限數字轉為字串, 寫於複本(R-ID-02); 刪除不經clearXSS(R-XSS-01)
465
+ if (idCast) {
466
+ input = castIdRows(input)
467
+ }
468
+
293
469
  //del
294
470
  r = await wo.del(input)
295
471
  .catch((msg) => {
@@ -309,8 +485,14 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
309
485
  }
310
486
  else if (mode === 'delAll') {
311
487
 
312
- //delAll
313
- r = await wo.delAll(input)
488
+ //check, 條件若給予須為一般物件(R-INPUT-01): 各後端對非物件之條件一律視為無條件而刪除全表; 未給(undefined)與空物件依規格為刪除全部
489
+ if (input !== undefined && !isobj(input)) {
490
+ console.log(`${woName}.delAll invalid find`, input)
491
+ return Promise.reject('invalid find of delAll, need an object')
492
+ }
493
+
494
+ //delAll, 條件中主鍵之比較值轉型(R-ID-02)
495
+ r = await wo.delAll(idCast ? castIdFind(input) : input)
314
496
  .catch((msg) => {
315
497
  console.log(`${woName}.delAll catch`, msg)
316
498
  err = 'failed to delete data'
@@ -324,6 +506,36 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
324
506
  return Promise.reject('invalid input of mix, need the result object of ltdtDiffByKey')
325
507
  }
326
508
 
509
+ //check, add、del、diff若給予須為陣列(R-INPUT-01), 否則各段之回傳將逐欄位產生
510
+ for (let key of ['add', 'del', 'diff']) {
511
+ if (input[key] !== undefined && !isarr(input[key])) {
512
+ return Promise.reject('invalid input of mix, need the result object of ltdtDiffByKey')
513
+ }
514
+ }
515
+
516
+ //check, 新增與修改之各筆須為一般物件, 於任何步驟寫入之前整批拒絕(R-INPUT-01)
517
+ let msgsNotObj = []
518
+ for (let [key, msg] of [['add', 'failed to insert data'], ['diff', 'failed to update data']]) {
519
+ let fails = getItemsNotObj(get(input, key, []))
520
+ if (size(fails) > 0) {
521
+ console.log(`${woName}.mix ${key} data is not an object`, fails)
522
+ msgsNotObj.push(getMsgFail(`${msg}, not an object`, fails))
523
+ }
524
+ }
525
+ if (size(msgsNotObj) > 0) {
526
+ return Promise.reject(join(msgsNotObj, '; '))
527
+ }
528
+
529
+ //castIdRows, 各段主鍵之有限數字轉為字串, 寫於複本(R-ID-02)
530
+ if (idCast) {
531
+ input = { ...input }
532
+ for (let key of ['add', 'del', 'diff']) {
533
+ if (isarr(input[key])) {
534
+ input[key] = castIdRows(input[key])
535
+ }
536
+ }
537
+ }
538
+
327
539
  //check, 新增與修改之主鍵會被clearXSS改寫者, 於任何步驟寫入之前整批拒絕
328
540
  let msgsIdXSS = []
329
541
  for (let [key, msg] of [['add', 'failed to insert data'], ['diff', 'failed to update data']]) {
@@ -337,6 +549,13 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
337
549
  return Promise.reject(join(msgsIdXSS, '; '))
338
550
  }
339
551
 
552
+ //check, add與del、del與diff同鍵者, 於任何步驟寫入之前整批拒絕(R-MIX-01)
553
+ let msgsSameId = getMsgsMixSameId(input)
554
+ if (size(msgsSameId) > 0) {
555
+ console.log(`${woName}.mix same id across steps`, msgsSameId)
556
+ return Promise.reject(join(msgsSameId, '; '))
557
+ }
558
+
340
559
  //rt,r
341
560
  let rt
342
561
  r = [] //default to array
@@ -0,0 +1,113 @@
1
+ import fs from 'fs'
2
+ import assert from 'assert'
3
+ import WOrm from 'w-orm-lowdb/src/WOrmLowdb.mjs'
4
+ import ltdtDiffByKey from 'wsemi/src/ltdtDiffByKey.mjs'
5
+ import ds from '../schema/index.mjs'
6
+ import WServOrm from '../src/WServOrm.mjs'
7
+
8
+
9
+ //以真ORM(w-orm-lowdb, 主鍵固定為字串id, 數字主鍵原會被其以genIDSeq取代)經procOrm驗證R-ID-02、R-MIX-01; schema之tests表之id為STRING pk
10
+ let fd = './test/_tmp/api-pkcast'
11
+
12
+ async function getReject(pm) {
13
+ try {
14
+ await pm
15
+ }
16
+ catch (err) {
17
+ return err
18
+ }
19
+ throw new Error('預期reject但resolve')
20
+ }
21
+
22
+
23
+ describe('數字主鍵接w-orm-lowdb', function() {
24
+
25
+ let so = null
26
+ let proc = (mode, input) => so.procOrm('u1', 'tests', mode, input)
27
+ let rows = async () => (await so.woItems.tests.select()).map((v) => [v.id, v.isActive])
28
+ let quiet = async (fn) => {
29
+ let logOrig = console.log
30
+ console.log = () => {}
31
+ try {
32
+ return await fn()
33
+ }
34
+ finally {
35
+ console.log = logOrig
36
+ }
37
+ }
38
+
39
+ before(function() {
40
+ fs.rmSync(fd, { recursive: true, force: true })
41
+ fs.mkdirSync(fd, { recursive: true })
42
+ so = WServOrm(ds, WOrm, `${fd}/db.json`, 'worm')
43
+ })
44
+
45
+ beforeEach(async function() {
46
+ await so.woItems.tests.delAll()
47
+ })
48
+
49
+ after(async function() {
50
+ await so.closeAll()
51
+ fs.rmSync(fd, { recursive: true, force: true })
52
+ })
53
+
54
+ it('A08 數字主鍵存為其字串形式, 重送與修改命中同一筆, 以數字或字串皆可查可刪', async function() {
55
+ //R-ID-02 規則2: 回報 = 實際; 重送不增筆(原每次另增genIDSeq之列); 寫入、查詢、刪除同一規則
56
+ let r = await proc('insert', [{ id: 123, isActive: 'y' }])
57
+ assert.deepStrictEqual(r, [{ id: '123' }])
58
+ await proc('insert', [{ id: 123, isActive: 'y' }])
59
+ await proc('save', [{ id: 123, isActive: 'n' }])
60
+ await proc('mix', { diff: [{ id: 123, isActive: 'm' }] })
61
+ assert.deepStrictEqual(await rows(), [['123', 'm']])
62
+ assert.strictEqual((await proc('select', { id: 123 })).length, 1)
63
+ assert.strictEqual((await proc('select', { id: '123' })).length, 1)
64
+ let rd = await proc('del', [{ id: 123 }])
65
+ assert.deepStrictEqual(rd, [{ n: 1, nDeleted: 1, ok: 1 }])
66
+ assert.deepStrictEqual(await rows(), [])
67
+ })
68
+
69
+ it('A09 表內已有他方以字串寫入之同值主鍵: 以數字save覆寫該列, insert略過而回報字串主鍵', async function() {
70
+ //R-ID-02 規則2: 123與'123'為同一筆(對外行為變更, 原另增genIDSeq之列)
71
+ await so.woItems.tests.insert([{ id: '777', isActive: 'A' }])
72
+ let rs = await proc('save', [{ id: 777, isActive: 'B' }])
73
+ assert.deepStrictEqual(rs, [{ id: '777' }])
74
+ let ri = await proc('insert', [{ id: 777, isActive: 'C' }])
75
+ assert.deepStrictEqual(ri, [{ id: '777' }])
76
+ assert.deepStrictEqual(await rows(), [['777', 'B']])
77
+ })
78
+
79
+ it('A10 以ltdtDiffByKey嚴格比對產生之add 888、del 888字串, 整批reject且表內不變', async function() {
80
+ //R-MIX-01 規則1、2: 原為insert略過、del刪除而resolve遺失資料
81
+ await so.woItems.tests.insert([{ id: '888', isActive: 'y' }])
82
+ let old = await so.woItems.tests.select()
83
+ let input = ltdtDiffByKey(old, [{ ...old[0], id: 888 }], 'id', { withInfor: false })
84
+ assert.deepStrictEqual([input.add.length, input.del.length], [1, 1])
85
+ let err = await quiet(() => getReject(proc('mix', input)))
86
+ assert.strictEqual(err, 'invalid input of mix, same id in add and del: add[0], del[0]')
87
+ assert.deepStrictEqual(await rows(), [['888', 'y']])
88
+ })
89
+
90
+ it('A11 同批之數字與其字串形式: 表內一筆(首筆內容), 回報兩筆同鍵', async function() {
91
+ //R-ID-02 規則2; insert回傳之id含被略過者
92
+ let r = await proc('insert', [{ id: 999, isActive: 'p' }, { id: '999', isActive: 'q' }])
93
+ assert.deepStrictEqual(r, [{ id: '999' }, { id: '999' }])
94
+ assert.deepStrictEqual(await rows(), [['999', 'p']])
95
+ })
96
+
97
+ it('A12 查詢與delAll之條件以數字給予: 等值、$in、$ne、$not命中字串主鍵, 範圍依字典序', async function() {
98
+ //R-ID-02 規則2(查詢條件)與已知限制(範圍依字典序: '40' < '5')
99
+ await proc('insert', [{ id: 5, isActive: 'y' }, { id: 40, isActive: 'y' }, { id: 'abc', isActive: 'y' }])
100
+ let ids = async (find) => (await proc('select', find)).map((v) => v.id).sort()
101
+ assert.deepStrictEqual(await ids({ id: 5 }), ['5'])
102
+ assert.deepStrictEqual(await ids({ id: { $in: [5, 40] } }), ['40', '5'])
103
+ assert.deepStrictEqual(await ids({ id: { $ne: 5 } }), ['40', 'abc'])
104
+ assert.deepStrictEqual(await ids({ id: { $not: { $eq: 5 } } }), ['40', 'abc'])
105
+ assert.deepStrictEqual(await ids({ id: { $lt: 5 } }), ['40'])
106
+ let r1 = await proc('delAll', { id: { $in: [40] } })
107
+ assert.deepStrictEqual(r1, { n: 1, nDeleted: 1, ok: 1 })
108
+ let r2 = await proc('delAll', { id: 5 })
109
+ assert.deepStrictEqual(r2, { n: 1, nDeleted: 1, ok: 1 })
110
+ assert.deepStrictEqual(await rows(), [['abc', 'y']])
111
+ })
112
+
113
+ })
@@ -44,17 +44,18 @@ describe('procOrm接w-orm-lowdb', function() {
44
44
  })
45
45
 
46
46
  it('A02 del含無效主鍵', async function() {
47
- //R02: 逐筆ok為0即reject並列出失敗主鍵; R10: 單筆失敗不中斷整批, 有效筆已刪除
48
- let err = await getReject(proc('del', [{ id: 'a1' }, { id: 123 }]))
49
- assert.strict.equal(err, 'failed to delete data: id[123]')
47
+ //R02: 逐筆ok為0即reject並列出失敗筆(無有效主鍵者列位置); R10: 單筆失敗不中斷整批, 有效筆已刪除
48
+ //無效主鍵取非字串非數字之值: 數字依R-ID-02轉為字串後為有效主鍵(未命中), 不再觸發逐筆失敗
49
+ let err = await getReject(proc('del', [{ id: 'a1' }, { id: true }]))
50
+ assert.strict.equal(err, 'failed to delete data: data[1]')
50
51
  let rs = await proc('select', { id: 'a1' })
51
52
  assert.strict.equal(rs.length, 0)
52
53
  })
53
54
 
54
55
  it('A03 mix之del含無效主鍵', async function() {
55
- //R03: del之逐筆失敗不中斷diff, diff已寫入, 最後reject列出del之失敗
56
- let err = await getReject(proc('mix', { del: [{ id: 456 }], diff: [{ id: 'a2', isActive: 'y' }] }))
57
- assert.strict.equal(err, 'failed to delete data: id[456]')
56
+ //R03: del之逐筆失敗不中斷diff, diff已寫入, 最後reject列出del之失敗(未帶主鍵之列以位置列出)
57
+ let err = await getReject(proc('mix', { del: [{ id: null }], diff: [{ id: 'a2', isActive: 'y' }] }))
58
+ assert.strict.equal(err, 'failed to delete data: data[0]')
58
59
  let rs = await proc('select', { id: 'a2' })
59
60
  assert.strict.equal(rs.length, 1)
60
61
  })