w-orm-lowdb 1.0.31 → 1.0.33
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.
- package/README.md +33 -1
- package/dist/w-orm-lowdb.umd.js +2 -2
- package/dist/w-orm-lowdb.umd.js.map +1 -1
- package/docs/WOrmLowdb.html +41 -15
- package/docs/WOrmLowdb.mjs.html +150 -22
- package/docs/index.html +1 -1
- package/g-basic.mjs +1 -1
- package/package.json +3 -3
- package/src/WOrmLowdb.mjs +149 -21
- package/test/unit-find-whitelist.test.mjs +999 -0
package/docs/WOrmLowdb.mjs.html
CHANGED
|
@@ -66,6 +66,7 @@ import isestr from 'wsemi/src/isestr.mjs'
|
|
|
66
66
|
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
|
+
import cvOrmFind from 'wsemi/src/cvOrmFind.mjs'
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
//kpGqueue, 以資料庫檔案路徑為鍵之全域序列化佇列, 詳見下方serialize說明
|
|
@@ -189,6 +190,93 @@ function WOrmLowdb(opt = {}) {
|
|
|
189
190
|
return pm
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
//reIsoDate, 西元0000-9999年之toISOString()恆為24字元YYYY-MM-DDTHH:mm:ss.sssZ, 字典序即時間序;
|
|
194
|
+
//年份<0或>9999者帶-yyyyyy-/+yyyyyy-前綴(27字元), 且'+'<'-'<數字, 字典序與時間序顛倒, 故不符此形者視為範圍外
|
|
195
|
+
let reIsoDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
|
196
|
+
|
|
197
|
+
//pathKey, 路徑段(欄名或運算子名,皆已通過cvOrmFind之文法): 過長者截短, 規則同cvOrmFind之訊息(單鍵>30字元取前30加...,
|
|
198
|
+
//截斷處切開代理對而成孤立之高代理碼元者, cvOrmFind以?取代)
|
|
199
|
+
let pathKey = (k) => {
|
|
200
|
+
if (k.length <= 30) {
|
|
201
|
+
return k
|
|
202
|
+
}
|
|
203
|
+
let t = k.slice(0, 30)
|
|
204
|
+
let c = t.charCodeAt(29)
|
|
205
|
+
if (c >= 0xD800 && c <= 0xDBFF) {
|
|
206
|
+
t = `${t.slice(0, 29)}?`
|
|
207
|
+
}
|
|
208
|
+
return `${t}...`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//pathStr, 路徑字串化: 全路徑>120字元取...加末100字元, 規則同cvOrmFind之訊息
|
|
212
|
+
let pathStr = (s) => {
|
|
213
|
+
return s.length > 120 ? `...${s.slice(-100)}` : s
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
//isoFind, 把cvOrmFind所回傳複本內之Date就地轉為ISO字串(轉譯層): 數據以JSON存檔, Date於寫入時已成toISOString()之字串,
|
|
217
|
+
//條件內之Date不轉則恆不命中; 複本僅含null原型物件、密集陣列、純量與有效Date(無共享參照、無環), 就地轉換不影響呼叫端之find
|
|
218
|
+
//轉換所得之字串須為reIsoDate之形(檢查之值即使用之值), 範圍外(年份<0或>9999)者fail-closed: 拋出invalid find(帳本R-FIND-02), 不回顯值, p為路徑
|
|
219
|
+
let isoFind = (v, p = 'find') => {
|
|
220
|
+
if (v instanceof Date) {
|
|
221
|
+
let s = v.toISOString()
|
|
222
|
+
if (!reIsoDate.test(s)) {
|
|
223
|
+
throw new Error(`invalid find: Date year is out of range 0000-9999 at [${pathStr(p)}]`)
|
|
224
|
+
}
|
|
225
|
+
return s
|
|
226
|
+
}
|
|
227
|
+
if (v !== null && typeof v === 'object') {
|
|
228
|
+
|
|
229
|
+
//物件與陣列皆以Object.keys走訪(陣列為密集, 鍵即索引)
|
|
230
|
+
let isArr = Array.isArray(v)
|
|
231
|
+
for (let k of Object.keys(v)) {
|
|
232
|
+
v[k] = isoFind(v[k], isArr ? `${p}[${k}]` : `${p}.${pathKey(k)}`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return v
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//hasProtoName, 檢核後複本之欄名是否同於Object.prototype之成員(constructor、toString、valueOf、hasOwnProperty等):
|
|
239
|
+
//此類欄名屬白名單內之合法欄名, 但mingo以屬性存取取值, 數據無此自有欄位時會取到繼承之成員(函數)而非缺欄,
|
|
240
|
+
//使{constructor:null}無命中、{constructor:{$ne:null}}命中全部(delAll因而刪光); 欄名只會出現於複本之鍵(含$and/$or內之子條件)
|
|
241
|
+
let hasProtoName = (fc) => {
|
|
242
|
+
for (let k of Object.keys(fc)) {
|
|
243
|
+
if (k === '$and' || k === '$or') {
|
|
244
|
+
for (let sub of fc[k]) {
|
|
245
|
+
if (hasProtoName(sub)) {
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else if (k in Object.prototype) {
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
//mkMatch, 依檢核後複本建立逐筆命中判定, select與delAll共用同一判定(系列最低要求①: select命中之數據即delAll刪除之數據);
|
|
258
|
+
//欄名同於Object.prototype成員者, 改以null原型之淺複本(只含自有可列舉欄位)判定, 令其與一般不存在之欄位同為缺欄語意, 命中者仍回傳原數據
|
|
259
|
+
let mkMatch = (fc) => {
|
|
260
|
+
let q = new Query(fc)
|
|
261
|
+
if (!hasProtoName(fc)) {
|
|
262
|
+
return (v) => q.test(v)
|
|
263
|
+
}
|
|
264
|
+
return (v) => q.test(v !== null && typeof v === 'object' ? Object.assign(Object.create(null), v) : v)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
//procFind, 條件之入口檢核(帳本R-FIND-01), 供select與delAll於serialize佇列之外、讀取之前呼叫:
|
|
268
|
+
//不合白名單者發出error事件(於reject之前)並回{err}, 由呼叫端reject, 不占用佇列; 合法者回{fc}為全新複本(null原型, 與呼叫端之find脫勾),
|
|
269
|
+
//其後一律只用此複本; 複本無自有鍵(未給、undefined、{})為全部
|
|
270
|
+
let procFind = (mode, find) => {
|
|
271
|
+
try {
|
|
272
|
+
return { fc: isoFind(cvOrmFind(find)) }
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
emitError(mode, null, err)
|
|
276
|
+
return { err }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
192
280
|
//emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
|
|
193
281
|
//一律包try/catch, 令訂閱函數自身拋錯不影響本次操作之結果
|
|
194
282
|
let emitChange = (mode, data, res) => {
|
|
@@ -300,11 +388,16 @@ function WOrmLowdb(opt = {}) {
|
|
|
300
388
|
/**
|
|
301
389
|
* 查詢數據
|
|
302
390
|
*
|
|
391
|
+
* 條件find僅支援系列白名單(規則同delAll之第1-3點,見delAll),與delAll共用同一檢核,select(find)命中之數據即delAll(find)刪除之數據
|
|
392
|
+
* 條件內之Date年份須介於0000-9999,範圍外整批reject;數據側已存入之範圍外Date無法以Date條件正確比較(規則同delAll之第6點,見README)
|
|
393
|
+
*
|
|
303
394
|
* @memberOf WOrmLowdb
|
|
304
|
-
* @param {Object} [find={}] 輸入查詢條件物件
|
|
395
|
+
* @param {Object} [find={}] 輸入查詢條件物件(系列白名單,規則同delAll之第1-3點); 未給、undefined或{}回傳全部數據; 非條件物件或白名單外運算子整批reject
|
|
305
396
|
* @returns {Promise} 回傳Promise,resolve回傳數據陣列,無符合數據回傳空陣列,reject回傳錯誤訊息
|
|
306
397
|
*/
|
|
307
|
-
async function selectCore(find
|
|
398
|
+
async function selectCore(find) {
|
|
399
|
+
|
|
400
|
+
//find, 入口檢核後之複本(procFind), 不再讀取呼叫端之條件
|
|
308
401
|
let isErr = false
|
|
309
402
|
|
|
310
403
|
//res
|
|
@@ -314,15 +407,11 @@ function WOrmLowdb(opt = {}) {
|
|
|
314
407
|
//ltdt
|
|
315
408
|
let ltdt = await getData()
|
|
316
409
|
|
|
317
|
-
//filter
|
|
318
|
-
if (
|
|
410
|
+
//filter, 複本無自有鍵(未給、undefined、{})為全部
|
|
411
|
+
if (Reflect.ownKeys(find).length > 0) {
|
|
319
412
|
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
// console.log('q', q)
|
|
323
|
-
|
|
324
|
-
//find
|
|
325
|
-
res = q.find(ltdt).all()
|
|
413
|
+
//find, 與delAll共用同一逐筆判定
|
|
414
|
+
res = ltdt.filter(mkMatch(find))
|
|
326
415
|
// console.log('res', res)
|
|
327
416
|
|
|
328
417
|
}
|
|
@@ -354,7 +443,14 @@ function WOrmLowdb(opt = {}) {
|
|
|
354
443
|
return res
|
|
355
444
|
}
|
|
356
445
|
async function select(find = {}) {
|
|
357
|
-
|
|
446
|
+
|
|
447
|
+
//check find, 置於serialize佇列之外, 錯誤不占佇列(R-FIND-01)
|
|
448
|
+
let pf = procFind('select', find)
|
|
449
|
+
if (pf.err) {
|
|
450
|
+
return Promise.reject(pf.err)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return serialize(() => selectCore(pf.fc))
|
|
358
454
|
}
|
|
359
455
|
|
|
360
456
|
/**
|
|
@@ -1009,13 +1105,39 @@ function WOrmLowdb(opt = {}) {
|
|
|
1009
1105
|
}
|
|
1010
1106
|
|
|
1011
1107
|
/**
|
|
1012
|
-
*
|
|
1108
|
+
* 依條件刪除多筆數據(破壞性操作,須審慎使用,非必要不用;刪除單筆請用del)
|
|
1109
|
+
*
|
|
1110
|
+
* 使用須知:
|
|
1111
|
+
* 1. 不給條件即刪除全部數據: find未給、為undefined或為{}時,刪除資料表內全部數據,不另行確認。
|
|
1112
|
+
* 給了條件才是部分刪除。
|
|
1113
|
+
* 2. 傳了值卻不是條件物件(null、陣列含[]與[{...}]、字串、數值、布林、函數、Date、RegExp、Map、Set、類別實例等)
|
|
1114
|
+
* 一律整批reject,不視為{},不刪除任何數據。
|
|
1115
|
+
* 3. 條件僅支援系列白名單: 欄位等值{欄:純量}(多欄為AND)、欄位運算子$eq $ne $gt $gte $lt $lte(值為純量)與
|
|
1116
|
+
* $in $nin(值為純量陣列)、文件層$and $or(值為條件物件之非空陣列)。純量為字串(不含NUL字元與孤立代理字元)、有限數字、布林、有效Date(年份須介於0000-9999,範圍外整批reject,見第6點);
|
|
1117
|
+
* null僅可用於欄位等值、$eq、$ne。白名單外之運算子($not $nor $regex $options $exists $where $expr $like $ilike等)、
|
|
1118
|
+
* 欄位層$and/$or、子查詢、函數值、Symbol鍵、undefined值、空的$nin/$and/$or等一律整批reject,
|
|
1119
|
+
* 訊息為invalid find: 原因 at [路徑],且不刪除任何數據。
|
|
1120
|
+
* 4. 與select共用同一檢核與同一條件,select(find)命中之數據即本函數刪除之數據;
|
|
1121
|
+
* 建議先以select(find)確認命中範圍再刪除(useCache開啟且他行程已寫入時,select可能回傳快取,而本函數一律直讀資料庫)。
|
|
1122
|
+
* 5. 條件值若來自可能為undefined、null或空陣列之變數,呼叫端須先自行判斷;undefined值會reject而非被忽略,
|
|
1123
|
+
* 空的$nin會reject,$in:[]則為恆假(刪除0筆)。
|
|
1124
|
+
* 6. 語意差異(依後端,見README): MongoDB式: $ne(比較值非null時)與$nin會命中欄位為null或缺欄之數據,$ne:null則排除之;
|
|
1125
|
+
* 欄名同於Object.prototype成員(constructor、toString等)者,數據無此自有欄位時視為缺欄;
|
|
1126
|
+
* Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦會比較):條件內任何位置之Date其年份須介於0000-9999,
|
|
1127
|
+
* 範圍外(年份小於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。
|
|
1130
|
+
* 需明確表達時可用可攜寫法: 排除NULL為{$and:[{f:{$ne:null}},{f:{$ne:x}}]},含NULL為{$or:[{f:null},{f:{$ne:x}}]}。
|
|
1131
|
+
*
|
|
1132
|
+
* n與nDeleted皆為實際刪除筆數,不取全表筆數。條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤。
|
|
1013
1133
|
*
|
|
1014
1134
|
* @memberOf WOrmLowdb
|
|
1015
|
-
* @param {Object} [find={}]
|
|
1016
|
-
* @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},
|
|
1135
|
+
* @param {Object} [find={}] 輸入刪除條件物件,不給予find則代表刪除全部數據
|
|
1136
|
+
* @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},reject回傳錯誤訊息
|
|
1017
1137
|
*/
|
|
1018
|
-
async function delAllCore(find
|
|
1138
|
+
async function delAllCore(find) {
|
|
1139
|
+
|
|
1140
|
+
//find, 入口檢核後之複本(procFind), 不再讀取呼叫端之條件
|
|
1019
1141
|
let isErr = false
|
|
1020
1142
|
|
|
1021
1143
|
//res
|
|
@@ -1029,15 +1151,14 @@ function WOrmLowdb(opt = {}) {
|
|
|
1029
1151
|
let nAll = size(arrOld)
|
|
1030
1152
|
let nDel = 0
|
|
1031
1153
|
let arrNew = null
|
|
1032
|
-
if (
|
|
1154
|
+
if (Reflect.ownKeys(find).length > 0) {
|
|
1033
1155
|
|
|
1034
|
-
//
|
|
1035
|
-
let
|
|
1036
|
-
// console.log('q', q)
|
|
1156
|
+
//match, 與select共用同一逐筆判定
|
|
1157
|
+
let match = mkMatch(find)
|
|
1037
1158
|
|
|
1038
1159
|
//filter, 以與select同一條件逐筆判定, 保留未命中者
|
|
1039
1160
|
arrNew = filter(arrOld, (v) => {
|
|
1040
|
-
return !
|
|
1161
|
+
return !match(v)
|
|
1041
1162
|
})
|
|
1042
1163
|
|
|
1043
1164
|
//nDel
|
|
@@ -1094,7 +1215,14 @@ function WOrmLowdb(opt = {}) {
|
|
|
1094
1215
|
return res
|
|
1095
1216
|
}
|
|
1096
1217
|
async function delAll(find = {}) {
|
|
1097
|
-
|
|
1218
|
+
|
|
1219
|
+
//check find, 置於serialize佇列之外, 錯誤不占佇列(R-FIND-01)
|
|
1220
|
+
let pf = procFind('delAll', find)
|
|
1221
|
+
if (pf.err) {
|
|
1222
|
+
return Promise.reject(pf.err)
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
return serialize(() => delAllCore(pf.fc))
|
|
1098
1226
|
}
|
|
1099
1227
|
|
|
1100
1228
|
//bind
|
|
@@ -1125,7 +1253,7 @@ export default WOrmLowdb
|
|
|
1125
1253
|
<br class="clear">
|
|
1126
1254
|
|
|
1127
1255
|
<footer>
|
|
1128
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
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.
|
|
1129
1257
|
</footer>
|
|
1130
1258
|
|
|
1131
1259
|
<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
|
|
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.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
package/g-basic.mjs
CHANGED
|
@@ -156,7 +156,7 @@ async function test() {
|
|
|
156
156
|
let spc = await wo.select({ '$or': [{ '$and': [{ value: { '$ne': 123 } }, { value: { '$in': [123, 321, 123.456, 456] } }, { value: { '$nin': [456, 654] } }] }, { '$or': [{ value: { '$lte': -1 } }, { value: { '$gte': 400 } }] }] })
|
|
157
157
|
console.log('select by $or, $and, $ne, $in, $nin', spc)
|
|
158
158
|
|
|
159
|
-
// //select by regex
|
|
159
|
+
// //select by regex //系列條件白名單不支援$regex(含$options), 會整批reject(invalid find: unsupported operator), 須改為select後自行過濾
|
|
160
160
|
// let sr = await wo.select({ name: { $regex: 'PeT', $options: '$i' } })
|
|
161
161
|
// console.log('selectReg', sr)
|
|
162
162
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-orm-lowdb",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
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.
|
|
8
|
+
"wsemi": "^1.9.20"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"w-package-tools": "^1.1.
|
|
11
|
+
"w-package-tools": "^1.1.21"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "mocha --parallel \"test/*.test.mjs\" --timeout 180000",
|
package/src/WOrmLowdb.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import isestr from 'wsemi/src/isestr.mjs'
|
|
|
19
19
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
20
20
|
import isearr from 'wsemi/src/isearr.mjs'
|
|
21
21
|
import haskey from 'wsemi/src/haskey.mjs'
|
|
22
|
+
import cvOrmFind from 'wsemi/src/cvOrmFind.mjs'
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
//kpGqueue, 以資料庫檔案路徑為鍵之全域序列化佇列, 詳見下方serialize說明
|
|
@@ -142,6 +143,93 @@ function WOrmLowdb(opt = {}) {
|
|
|
142
143
|
return pm
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
//reIsoDate, 西元0000-9999年之toISOString()恆為24字元YYYY-MM-DDTHH:mm:ss.sssZ, 字典序即時間序;
|
|
147
|
+
//年份<0或>9999者帶-yyyyyy-/+yyyyyy-前綴(27字元), 且'+'<'-'<數字, 字典序與時間序顛倒, 故不符此形者視為範圍外
|
|
148
|
+
let reIsoDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
|
149
|
+
|
|
150
|
+
//pathKey, 路徑段(欄名或運算子名,皆已通過cvOrmFind之文法): 過長者截短, 規則同cvOrmFind之訊息(單鍵>30字元取前30加...,
|
|
151
|
+
//截斷處切開代理對而成孤立之高代理碼元者, cvOrmFind以?取代)
|
|
152
|
+
let pathKey = (k) => {
|
|
153
|
+
if (k.length <= 30) {
|
|
154
|
+
return k
|
|
155
|
+
}
|
|
156
|
+
let t = k.slice(0, 30)
|
|
157
|
+
let c = t.charCodeAt(29)
|
|
158
|
+
if (c >= 0xD800 && c <= 0xDBFF) {
|
|
159
|
+
t = `${t.slice(0, 29)}?`
|
|
160
|
+
}
|
|
161
|
+
return `${t}...`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//pathStr, 路徑字串化: 全路徑>120字元取...加末100字元, 規則同cvOrmFind之訊息
|
|
165
|
+
let pathStr = (s) => {
|
|
166
|
+
return s.length > 120 ? `...${s.slice(-100)}` : s
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//isoFind, 把cvOrmFind所回傳複本內之Date就地轉為ISO字串(轉譯層): 數據以JSON存檔, Date於寫入時已成toISOString()之字串,
|
|
170
|
+
//條件內之Date不轉則恆不命中; 複本僅含null原型物件、密集陣列、純量與有效Date(無共享參照、無環), 就地轉換不影響呼叫端之find
|
|
171
|
+
//轉換所得之字串須為reIsoDate之形(檢查之值即使用之值), 範圍外(年份<0或>9999)者fail-closed: 拋出invalid find(帳本R-FIND-02), 不回顯值, p為路徑
|
|
172
|
+
let isoFind = (v, p = 'find') => {
|
|
173
|
+
if (v instanceof Date) {
|
|
174
|
+
let s = v.toISOString()
|
|
175
|
+
if (!reIsoDate.test(s)) {
|
|
176
|
+
throw new Error(`invalid find: Date year is out of range 0000-9999 at [${pathStr(p)}]`)
|
|
177
|
+
}
|
|
178
|
+
return s
|
|
179
|
+
}
|
|
180
|
+
if (v !== null && typeof v === 'object') {
|
|
181
|
+
|
|
182
|
+
//物件與陣列皆以Object.keys走訪(陣列為密集, 鍵即索引)
|
|
183
|
+
let isArr = Array.isArray(v)
|
|
184
|
+
for (let k of Object.keys(v)) {
|
|
185
|
+
v[k] = isoFind(v[k], isArr ? `${p}[${k}]` : `${p}.${pathKey(k)}`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return v
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//hasProtoName, 檢核後複本之欄名是否同於Object.prototype之成員(constructor、toString、valueOf、hasOwnProperty等):
|
|
192
|
+
//此類欄名屬白名單內之合法欄名, 但mingo以屬性存取取值, 數據無此自有欄位時會取到繼承之成員(函數)而非缺欄,
|
|
193
|
+
//使{constructor:null}無命中、{constructor:{$ne:null}}命中全部(delAll因而刪光); 欄名只會出現於複本之鍵(含$and/$or內之子條件)
|
|
194
|
+
let hasProtoName = (fc) => {
|
|
195
|
+
for (let k of Object.keys(fc)) {
|
|
196
|
+
if (k === '$and' || k === '$or') {
|
|
197
|
+
for (let sub of fc[k]) {
|
|
198
|
+
if (hasProtoName(sub)) {
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (k in Object.prototype) {
|
|
204
|
+
return true
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//mkMatch, 依檢核後複本建立逐筆命中判定, select與delAll共用同一判定(系列最低要求①: select命中之數據即delAll刪除之數據);
|
|
211
|
+
//欄名同於Object.prototype成員者, 改以null原型之淺複本(只含自有可列舉欄位)判定, 令其與一般不存在之欄位同為缺欄語意, 命中者仍回傳原數據
|
|
212
|
+
let mkMatch = (fc) => {
|
|
213
|
+
let q = new Query(fc)
|
|
214
|
+
if (!hasProtoName(fc)) {
|
|
215
|
+
return (v) => q.test(v)
|
|
216
|
+
}
|
|
217
|
+
return (v) => q.test(v !== null && typeof v === 'object' ? Object.assign(Object.create(null), v) : v)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
//procFind, 條件之入口檢核(帳本R-FIND-01), 供select與delAll於serialize佇列之外、讀取之前呼叫:
|
|
221
|
+
//不合白名單者發出error事件(於reject之前)並回{err}, 由呼叫端reject, 不占用佇列; 合法者回{fc}為全新複本(null原型, 與呼叫端之find脫勾),
|
|
222
|
+
//其後一律只用此複本; 複本無自有鍵(未給、undefined、{})為全部
|
|
223
|
+
let procFind = (mode, find) => {
|
|
224
|
+
try {
|
|
225
|
+
return { fc: isoFind(cvOrmFind(find)) }
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
emitError(mode, null, err)
|
|
229
|
+
return { err }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
145
233
|
//emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
|
|
146
234
|
//一律包try/catch, 令訂閱函數自身拋錯不影響本次操作之結果
|
|
147
235
|
let emitChange = (mode, data, res) => {
|
|
@@ -253,11 +341,16 @@ function WOrmLowdb(opt = {}) {
|
|
|
253
341
|
/**
|
|
254
342
|
* 查詢數據
|
|
255
343
|
*
|
|
344
|
+
* 條件find僅支援系列白名單(規則同delAll之第1-3點,見delAll),與delAll共用同一檢核,select(find)命中之數據即delAll(find)刪除之數據
|
|
345
|
+
* 條件內之Date年份須介於0000-9999,範圍外整批reject;數據側已存入之範圍外Date無法以Date條件正確比較(規則同delAll之第6點,見README)
|
|
346
|
+
*
|
|
256
347
|
* @memberOf WOrmLowdb
|
|
257
|
-
* @param {Object} [find={}] 輸入查詢條件物件
|
|
348
|
+
* @param {Object} [find={}] 輸入查詢條件物件(系列白名單,規則同delAll之第1-3點); 未給、undefined或{}回傳全部數據; 非條件物件或白名單外運算子整批reject
|
|
258
349
|
* @returns {Promise} 回傳Promise,resolve回傳數據陣列,無符合數據回傳空陣列,reject回傳錯誤訊息
|
|
259
350
|
*/
|
|
260
|
-
async function selectCore(find
|
|
351
|
+
async function selectCore(find) {
|
|
352
|
+
|
|
353
|
+
//find, 入口檢核後之複本(procFind), 不再讀取呼叫端之條件
|
|
261
354
|
let isErr = false
|
|
262
355
|
|
|
263
356
|
//res
|
|
@@ -267,15 +360,11 @@ function WOrmLowdb(opt = {}) {
|
|
|
267
360
|
//ltdt
|
|
268
361
|
let ltdt = await getData()
|
|
269
362
|
|
|
270
|
-
//filter
|
|
271
|
-
if (
|
|
363
|
+
//filter, 複本無自有鍵(未給、undefined、{})為全部
|
|
364
|
+
if (Reflect.ownKeys(find).length > 0) {
|
|
272
365
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
// console.log('q', q)
|
|
276
|
-
|
|
277
|
-
//find
|
|
278
|
-
res = q.find(ltdt).all()
|
|
366
|
+
//find, 與delAll共用同一逐筆判定
|
|
367
|
+
res = ltdt.filter(mkMatch(find))
|
|
279
368
|
// console.log('res', res)
|
|
280
369
|
|
|
281
370
|
}
|
|
@@ -307,7 +396,14 @@ function WOrmLowdb(opt = {}) {
|
|
|
307
396
|
return res
|
|
308
397
|
}
|
|
309
398
|
async function select(find = {}) {
|
|
310
|
-
|
|
399
|
+
|
|
400
|
+
//check find, 置於serialize佇列之外, 錯誤不占佇列(R-FIND-01)
|
|
401
|
+
let pf = procFind('select', find)
|
|
402
|
+
if (pf.err) {
|
|
403
|
+
return Promise.reject(pf.err)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return serialize(() => selectCore(pf.fc))
|
|
311
407
|
}
|
|
312
408
|
|
|
313
409
|
/**
|
|
@@ -962,13 +1058,39 @@ function WOrmLowdb(opt = {}) {
|
|
|
962
1058
|
}
|
|
963
1059
|
|
|
964
1060
|
/**
|
|
965
|
-
*
|
|
1061
|
+
* 依條件刪除多筆數據(破壞性操作,須審慎使用,非必要不用;刪除單筆請用del)
|
|
1062
|
+
*
|
|
1063
|
+
* 使用須知:
|
|
1064
|
+
* 1. 不給條件即刪除全部數據: find未給、為undefined或為{}時,刪除資料表內全部數據,不另行確認。
|
|
1065
|
+
* 給了條件才是部分刪除。
|
|
1066
|
+
* 2. 傳了值卻不是條件物件(null、陣列含[]與[{...}]、字串、數值、布林、函數、Date、RegExp、Map、Set、類別實例等)
|
|
1067
|
+
* 一律整批reject,不視為{},不刪除任何數據。
|
|
1068
|
+
* 3. 條件僅支援系列白名單: 欄位等值{欄:純量}(多欄為AND)、欄位運算子$eq $ne $gt $gte $lt $lte(值為純量)與
|
|
1069
|
+
* $in $nin(值為純量陣列)、文件層$and $or(值為條件物件之非空陣列)。純量為字串(不含NUL字元與孤立代理字元)、有限數字、布林、有效Date(年份須介於0000-9999,範圍外整批reject,見第6點);
|
|
1070
|
+
* null僅可用於欄位等值、$eq、$ne。白名單外之運算子($not $nor $regex $options $exists $where $expr $like $ilike等)、
|
|
1071
|
+
* 欄位層$and/$or、子查詢、函數值、Symbol鍵、undefined值、空的$nin/$and/$or等一律整批reject,
|
|
1072
|
+
* 訊息為invalid find: 原因 at [路徑],且不刪除任何數據。
|
|
1073
|
+
* 4. 與select共用同一檢核與同一條件,select(find)命中之數據即本函數刪除之數據;
|
|
1074
|
+
* 建議先以select(find)確認命中範圍再刪除(useCache開啟且他行程已寫入時,select可能回傳快取,而本函數一律直讀資料庫)。
|
|
1075
|
+
* 5. 條件值若來自可能為undefined、null或空陣列之變數,呼叫端須先自行判斷;undefined值會reject而非被忽略,
|
|
1076
|
+
* 空的$nin會reject,$in:[]則為恆假(刪除0筆)。
|
|
1077
|
+
* 6. 語意差異(依後端,見README): MongoDB式: $ne(比較值非null時)與$nin會命中欄位為null或缺欄之數據,$ne:null則排除之;
|
|
1078
|
+
* 欄名同於Object.prototype成員(constructor、toString等)者,數據無此自有欄位時視為缺欄;
|
|
1079
|
+
* Date條件於本後端轉為ISO字串後以字典序比較(與字串型欄位亦會比較):條件內任何位置之Date其年份須介於0000-9999,
|
|
1080
|
+
* 範圍外(年份小於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。
|
|
1083
|
+
* 需明確表達時可用可攜寫法: 排除NULL為{$and:[{f:{$ne:null}},{f:{$ne:x}}]},含NULL為{$or:[{f:null},{f:{$ne:x}}]}。
|
|
1084
|
+
*
|
|
1085
|
+
* n與nDeleted皆為實際刪除筆數,不取全表筆數。條件無命中時回{n:0,nDeleted:0,ok:1},不視為錯誤。
|
|
966
1086
|
*
|
|
967
1087
|
* @memberOf WOrmLowdb
|
|
968
|
-
* @param {Object} [find={}]
|
|
969
|
-
* @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},
|
|
1088
|
+
* @param {Object} [find={}] 輸入刪除條件物件,不給予find則代表刪除全部數據
|
|
1089
|
+
* @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},reject回傳錯誤訊息
|
|
970
1090
|
*/
|
|
971
|
-
async function delAllCore(find
|
|
1091
|
+
async function delAllCore(find) {
|
|
1092
|
+
|
|
1093
|
+
//find, 入口檢核後之複本(procFind), 不再讀取呼叫端之條件
|
|
972
1094
|
let isErr = false
|
|
973
1095
|
|
|
974
1096
|
//res
|
|
@@ -982,15 +1104,14 @@ function WOrmLowdb(opt = {}) {
|
|
|
982
1104
|
let nAll = size(arrOld)
|
|
983
1105
|
let nDel = 0
|
|
984
1106
|
let arrNew = null
|
|
985
|
-
if (
|
|
1107
|
+
if (Reflect.ownKeys(find).length > 0) {
|
|
986
1108
|
|
|
987
|
-
//
|
|
988
|
-
let
|
|
989
|
-
// console.log('q', q)
|
|
1109
|
+
//match, 與select共用同一逐筆判定
|
|
1110
|
+
let match = mkMatch(find)
|
|
990
1111
|
|
|
991
1112
|
//filter, 以與select同一條件逐筆判定, 保留未命中者
|
|
992
1113
|
arrNew = filter(arrOld, (v) => {
|
|
993
|
-
return !
|
|
1114
|
+
return !match(v)
|
|
994
1115
|
})
|
|
995
1116
|
|
|
996
1117
|
//nDel
|
|
@@ -1047,7 +1168,14 @@ function WOrmLowdb(opt = {}) {
|
|
|
1047
1168
|
return res
|
|
1048
1169
|
}
|
|
1049
1170
|
async function delAll(find = {}) {
|
|
1050
|
-
|
|
1171
|
+
|
|
1172
|
+
//check find, 置於serialize佇列之外, 錯誤不占佇列(R-FIND-01)
|
|
1173
|
+
let pf = procFind('delAll', find)
|
|
1174
|
+
if (pf.err) {
|
|
1175
|
+
return Promise.reject(pf.err)
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return serialize(() => delAllCore(pf.fc))
|
|
1051
1179
|
}
|
|
1052
1180
|
|
|
1053
1181
|
//bind
|