w-serv-orm 1.1.16 → 1.1.18
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 +110 -0
- package/dist/w-serv-orm.umd.js +2 -2
- package/dist/w-serv-orm.umd.js.map +1 -1
- package/docs/WServOrm.html +11 -4
- package/docs/WServOrm.mjs.html +96 -3
- package/docs/baseline.mjs.html +624 -0
- package/docs/global.html +1786 -0
- package/docs/index.html +2 -2
- package/docs/isOrmFail.mjs.html +102 -0
- package/package.json +1 -1
- package/src/WServOrm.mjs +94 -1
- package/src/baseline.mjs +552 -0
- package/src/genTestData.mjs +6 -0
- package/src/getDb.mjs +1 -1
- package/src/getDbBackupAndRecover.mjs +44 -0
- package/src/getMapOrm.mjs +229 -12
- package/src/getProc.mjs +13 -4
- package/src/isOrmFail.mjs +30 -0
- package/test/api-guard.test.mjs +401 -0
- package/test/api-keepuser.test.mjs +131 -0
- package/test/api-procOrm.test.mjs +142 -0
- package/test/tools/memOrm.mjs +122 -0
- package/test/unit-baseline.test.mjs +766 -0
- package/test/unit-getDb.test.mjs +78 -0
- package/test/unit-guard.test.mjs +952 -0
- package/test/unit-isOrmFail.test.mjs +35 -0
- package/test/unit-keepuser.test.mjs +307 -0
- package/test/unit-mapOrm.test.mjs +213 -0
- package/test/unit-ormsites.test.mjs +61 -0
package/src/getMapOrm.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import isestr from 'wsemi/src/isestr.mjs'
|
|
|
16
16
|
import isnum from 'wsemi/src/isnum.mjs'
|
|
17
17
|
import clearXSS from 'wsemi/src/clearXSS.mjs'
|
|
18
18
|
import getItemsFail from 'w-data-collector/src/getItemsFail.mjs'
|
|
19
|
+
import isOrmFail from './isOrmFail.mjs'
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
function getMsgFail(msg, fails) {
|
|
@@ -423,6 +424,119 @@ function getRowId(row) {
|
|
|
423
424
|
}
|
|
424
425
|
|
|
425
426
|
|
|
427
|
+
function hasOwnerUserId(row) {
|
|
428
|
+
//寫入列上之userId是否為擁有者: 只認自有、可列舉之資料屬性且為非空字串(與getRowId同一認定, 不執行getter), 其餘(缺欄、空字串、非字串、繼承、不可列舉)視為未提供
|
|
429
|
+
if (typeof row !== 'object' || row === null) {
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
let d = Object.getOwnPropertyDescriptor(row, 'userId')
|
|
433
|
+
return d !== undefined && d.enumerable && !isAcc(d) && typeof d.value === 'string' && d.value !== ''
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
//procOrm第5參數opt允許之鍵(R-ONLYMOD-01、R-KEEPUSER-01), 皆為布林
|
|
438
|
+
const OPT_KEYS_PROC = ['mustExist', 'keepUserId']
|
|
439
|
+
|
|
440
|
+
//mustExist之存在檢查每次select之主鍵數上限(R-ONLYMOD-01 規則 11)
|
|
441
|
+
const EXIST_CHUNK = 1000
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
function getOptProc(mode, opt) {
|
|
445
|
+
//procOrm之第5參數opt: 未給(undefined、null)為空設定; 否則須為純物件且只含mustExist、keepUserId(皆布林), 其餘鍵、存取器、非布林者於任何讀寫之前reject
|
|
446
|
+
//拼錯之鍵(如mustexist)若被靜默忽略, 會令只改不增失效而重新長出已刪之列(keepUserId拼錯則擁有者被覆寫成操作者), 故fail-closed; 訊息不回顯呼叫端之值
|
|
447
|
+
//mustExist只適用save與del; keepUserId只適用insert(save現行即不覆寫userId, mix之新增為內部步驟不開放); 先逐一驗型別再驗mode, 結果與鍵之次序無關
|
|
448
|
+
let bad = (err) => ({ err, mustExist: false, keepUserId: false })
|
|
449
|
+
if (opt === undefined || opt === null) {
|
|
450
|
+
return { err: null, mustExist: false, keepUserId: false }
|
|
451
|
+
}
|
|
452
|
+
if (!isPlainObj(opt)) {
|
|
453
|
+
return bad('invalid opt of procOrm, need an object')
|
|
454
|
+
}
|
|
455
|
+
for (let k of Reflect.ownKeys(opt)) {
|
|
456
|
+
if (OPT_KEYS_PROC.indexOf(k) < 0) {
|
|
457
|
+
return bad('invalid opt of procOrm, unsupported key')
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
let flags = { mustExist: false, keepUserId: false }
|
|
461
|
+
for (let key of OPT_KEYS_PROC) {
|
|
462
|
+
let d = Object.getOwnPropertyDescriptor(opt, key)
|
|
463
|
+
if (d !== undefined) {
|
|
464
|
+
if (isAcc(d) || typeof d.value !== 'boolean') {
|
|
465
|
+
return bad(`invalid opt of procOrm, ${key} needs a boolean`)
|
|
466
|
+
}
|
|
467
|
+
flags[key] = d.value
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (flags.mustExist && mode !== 'save' && mode !== 'del') {
|
|
471
|
+
return bad('invalid opt of procOrm, mustExist supports only save and del')
|
|
472
|
+
}
|
|
473
|
+
if (flags.keepUserId && mode !== 'insert') {
|
|
474
|
+
return bad('invalid opt of procOrm, keepUserId supports only insert')
|
|
475
|
+
}
|
|
476
|
+
return { err: null, mustExist: flags.mustExist, keepUserId: flags.keepUserId }
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
async function hasMissingRow(wo, rows) {
|
|
481
|
+
//rows(寫入或刪除之列)是否有任一列不存在於資料表(含無有效主鍵者: 無從識別即視為不存在), 以select(id $in)分批查詢; true為有缺
|
|
482
|
+
//只認自有可列舉之資料屬性之id(getRowId), 與寫入與刪除實際使用之id同一; 比對以字串形式(整數主鍵與其字串視為同一)
|
|
483
|
+
//此讀取為ORM之select, ORM開啟useCache時可能非新鮮, 屬部署前提(單一行程、同表同一ORM實例、不經快取), 見規則帳本R-ONLYMOD-01
|
|
484
|
+
let ids = []
|
|
485
|
+
let seen = new Set()
|
|
486
|
+
for (let v of (isArr(rows) ? rows : [rows])) {
|
|
487
|
+
let id = getRowId(v)
|
|
488
|
+
if (!((typeof id === 'string' && id !== '') || isIdNumFinite(id))) {
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
491
|
+
if (!seen.has(String(id))) {
|
|
492
|
+
seen.add(String(id))
|
|
493
|
+
ids.push(id)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
//分批查詢: 各w-orm-*共用之條件檢核(wsemi cvOrmFind)限$in之元素數(預設10000, 只可下調), 超過者整批reject; 每批EXIST_CHUNK個, 任一批有缺即止
|
|
497
|
+
for (let k = 0; k < ids.length; k += EXIST_CHUNK) {
|
|
498
|
+
let part = ids.slice(k, k + EXIST_CHUNK)
|
|
499
|
+
let rs = await wo.select({ id: { $in: part } })
|
|
500
|
+
let found = new Set(map(isArr(rs) ? rs : [], (r) => String(get(r, 'id'))))
|
|
501
|
+
if (part.some((id) => !found.has(String(id)))) {
|
|
502
|
+
return true
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return false
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
function getItemsDupId(rows) {
|
|
510
|
+
//mustExist之del: 同批重複之有效主鍵(第2次以後出現者)之位置[{k}]; 無效主鍵者不計(由存在檢查處置)
|
|
511
|
+
//重複者若放行, 第1筆刪除後第2筆必未命中, 事後偵測雖會reject卻已刪除, 違反[任一列不存在即零刪除], 故於刪除之前整批拒絕
|
|
512
|
+
let seen = new Set()
|
|
513
|
+
let rs = []
|
|
514
|
+
let arr = isArr(rows) ? rows : [rows]
|
|
515
|
+
for (let k = 0; k < arr.length; k++) {
|
|
516
|
+
let id = getRowId(arr[k])
|
|
517
|
+
if (!((typeof id === 'string' && id !== '') || isIdNumFinite(id))) {
|
|
518
|
+
continue
|
|
519
|
+
}
|
|
520
|
+
let s = String(id)
|
|
521
|
+
if (seen.has(s)) {
|
|
522
|
+
rs.push({ k })
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
seen.add(s)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return rs
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
function hasNoHit(r, keyDel) {
|
|
533
|
+
//ORM之逐筆結果是否有已正常處理(ok非0)而未命中者(n為0; keyDel非null者另認該鍵為0, 如del之nDeleted): 表鎖紀律被破壞(檢查後、寫入前列被他方刪除)時之事後偵測
|
|
534
|
+
return (isArr(r) ? r : [r]).some((x) => {
|
|
535
|
+
return x !== null && typeof x === 'object' && x.ok !== 0 && (x.n === 0 || (keyDel !== null && x[keyDel] === 0))
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
|
|
426
540
|
function firstBadIndex(a) {
|
|
427
541
|
//陣列首個空洞或存取器索引, 回傳[索引, 'sparse'|'accessor'], 無則null; 成本為該索引之位置, 稀疏巨陣列於索引0即返回
|
|
428
542
|
//索引為存取器者, 其後各步驟(each、map以array[index]讀取)會執行使用端程式並可每次回傳不同列(外審 sol F01)
|
|
@@ -922,7 +1036,7 @@ function setOwnData(o, k, v) {
|
|
|
922
1036
|
}
|
|
923
1037
|
|
|
924
1038
|
|
|
925
|
-
function adjustData(ds, woName, userId, data, mode = 'save') { //mode: 'insert', 'save', 'insert+save'
|
|
1039
|
+
function adjustData(ds, woName, userId, data, mode = 'save', keepUserId = false) { //mode: 'insert', 'save', 'insert+save'; keepUserId只由insert傳true(R-KEEPUSER-01)
|
|
926
1040
|
|
|
927
1041
|
//check
|
|
928
1042
|
if (!iseobj(ds)) {
|
|
@@ -946,9 +1060,12 @@ function adjustData(ds, woName, userId, data, mode = 'save') { //mode: 'insert',
|
|
|
946
1060
|
//userId, timeCreate
|
|
947
1061
|
if (mode.indexOf('insert') >= 0) {
|
|
948
1062
|
|
|
949
|
-
//userId
|
|
1063
|
+
//userId, keepUserId者(opt.keepUserId, 列上userId為擁有者而非建立者)逐列保留自有非空字串之userId, 其餘以操作者補; userIdUpdate不受影響, 恆為操作者
|
|
950
1064
|
if (keys.indexOf('userId') >= 0) {
|
|
951
1065
|
each(data, (v, k) => {
|
|
1066
|
+
if (keepUserId && hasOwnerUserId(data[k])) {
|
|
1067
|
+
return
|
|
1068
|
+
}
|
|
952
1069
|
setOwnData(data[k], 'userId', userId)
|
|
953
1070
|
})
|
|
954
1071
|
}
|
|
@@ -1028,11 +1145,11 @@ function adjustData(ds, woName, userId, data, mode = 'save') { //mode: 'insert',
|
|
|
1028
1145
|
}
|
|
1029
1146
|
|
|
1030
1147
|
|
|
1031
|
-
async function mapOrm(ds, woItems, userId, woName, mode, input) {
|
|
1148
|
+
async function mapOrm(ds, woItems, userId, woName, mode, input, opt) {
|
|
1032
1149
|
//邊界保證(R-ERR-01 規則5): execMapOrm之檢查、轉型與ORM呼叫階段, 非字串之例外(RangeError、使用端物件之Proxy陷阱/toString拋錯、woItems之非ORM值等)
|
|
1033
1150
|
//一律console.log原文後以固定字串reject, 不轉出例外物件與其拋出之值; 字串(各處之固定訊息)原樣轉出
|
|
1034
1151
|
try {
|
|
1035
|
-
return await execMapOrm(ds, woItems, userId, woName, mode, input)
|
|
1152
|
+
return await execMapOrm(ds, woItems, userId, woName, mode, input, opt)
|
|
1036
1153
|
}
|
|
1037
1154
|
catch (e) {
|
|
1038
1155
|
if (typeof e === 'string') {
|
|
@@ -1044,7 +1161,7 @@ async function mapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1044
1161
|
}
|
|
1045
1162
|
|
|
1046
1163
|
|
|
1047
|
-
async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
1164
|
+
async function execMapOrm(ds, woItems, userId, woName, mode, input, opt) {
|
|
1048
1165
|
//由woName自動綁定至woItems內的ORM物件
|
|
1049
1166
|
let err = null
|
|
1050
1167
|
let r
|
|
@@ -1065,6 +1182,12 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1065
1182
|
//idCast, 主鍵之有限數字以其字串形式處置(R-ID-02), 寫入、刪除與查詢條件共用同一判定
|
|
1066
1183
|
let idCast = isIdCast(ds, woName)
|
|
1067
1184
|
|
|
1185
|
+
//optProc, 第5參數opt(只改不增、刪除回報未命中, R-ONLYMOD-01), 於任何讀寫之前檢查
|
|
1186
|
+
let optProc = getOptProc(mode, opt)
|
|
1187
|
+
if (optProc.err !== null) {
|
|
1188
|
+
return Promise.reject(optProc.err)
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1068
1191
|
if (mode === 'select') {
|
|
1069
1192
|
|
|
1070
1193
|
//check, 條件若給予須為一般物件且不含自有__proto__鍵(R-INPUT-01 規則3、R-OBJ-01 規則3), 於轉型與查詢之前
|
|
@@ -1130,7 +1253,7 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1130
1253
|
|
|
1131
1254
|
//adjustData
|
|
1132
1255
|
try {
|
|
1133
|
-
input = adjustData(ds, woName, userId, input, mode)
|
|
1256
|
+
input = adjustData(ds, woName, userId, input, mode, optProc.keepUserId)
|
|
1134
1257
|
}
|
|
1135
1258
|
catch (e) {
|
|
1136
1259
|
console.log(`${woName}.adjustData catch`, e.toString())
|
|
@@ -1138,6 +1261,23 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1138
1261
|
return Promise.reject(err)
|
|
1139
1262
|
}
|
|
1140
1263
|
|
|
1264
|
+
//exist, 只改不增(opt.mustExist, R-ONLYMOD-01): 寫入之前檢查全部列(含補主鍵後)皆已存在, 任一列不存在(含無主鍵之列)即整批reject 'rowNotFound'而不寫入任何一筆
|
|
1265
|
+
//檢查與寫入非原子, 呼叫端須已持有該表之表鎖(withTabLocks、guardWrite)
|
|
1266
|
+
if (mode === 'save' && optProc.mustExist) {
|
|
1267
|
+
let missing = await hasMissingRow(wo, input)
|
|
1268
|
+
.catch((msg) => {
|
|
1269
|
+
console.log(`${woName}.save exist check catch`, msg)
|
|
1270
|
+
err = 'failed to save data'
|
|
1271
|
+
})
|
|
1272
|
+
if (isestr(err)) {
|
|
1273
|
+
return Promise.reject(err)
|
|
1274
|
+
}
|
|
1275
|
+
if (missing) {
|
|
1276
|
+
console.log(`${woName}.save row not found`)
|
|
1277
|
+
return Promise.reject('rowNotFound')
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1141
1281
|
//save
|
|
1142
1282
|
if (mode === 'insert') {
|
|
1143
1283
|
r = await wo.insert(input)
|
|
@@ -1147,7 +1287,8 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1147
1287
|
})
|
|
1148
1288
|
}
|
|
1149
1289
|
else if (mode === 'save') {
|
|
1150
|
-
|
|
1290
|
+
//只改不增者轉傳autoInsert:false(各ORM於未命中時不寫入); 其餘維持單參數呼叫
|
|
1291
|
+
r = await (optProc.mustExist ? wo.save(input, { autoInsert: false }) : wo.save(input))
|
|
1151
1292
|
.catch((msg) => {
|
|
1152
1293
|
console.log(`${woName}.save catch`, msg)
|
|
1153
1294
|
err = 'failed to save data'
|
|
@@ -1159,14 +1300,31 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1159
1300
|
return Promise.reject(err)
|
|
1160
1301
|
}
|
|
1161
1302
|
|
|
1303
|
+
//check, insert之錯誤皆屬整批性而reject, 其回傳為聚合物件且ok恆為1; 聚合結果ok為0或逐筆陣列任一筆ok為0者(ORM違約, 見isOrmFail)視為失敗, 不可回成功, 以固定字串整批reject
|
|
1304
|
+
if (mode === 'insert' && isOrmFail(r)) {
|
|
1305
|
+
console.log(`${woName}.insert fail`, r)
|
|
1306
|
+
return Promise.reject('failed to insert data')
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1162
1309
|
//check, save單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
|
|
1163
|
-
//insert之錯誤皆屬整批性而reject, 其回傳為聚合物件而非逐筆陣列, 故不須檢查
|
|
1164
1310
|
if (mode === 'save') {
|
|
1165
1311
|
let fails = getItemsFail(r, input)
|
|
1166
1312
|
if (size(fails) > 0) {
|
|
1167
1313
|
console.log(`${woName}.save fail`, fails)
|
|
1168
1314
|
return Promise.reject(getMsgFail('failed to save data', getItemsFail(r, inputOrig)))
|
|
1169
1315
|
}
|
|
1316
|
+
|
|
1317
|
+
//check, 回傳聚合物件而ok為0(非契約形狀)者無從逐筆列出, 仍視為失敗, 固定字串reject(R-ORM-RESULT-01); 陣列形之失敗已於上方列出
|
|
1318
|
+
if (isOrmFail(r)) {
|
|
1319
|
+
console.log(`${woName}.save fail`, r)
|
|
1320
|
+
return Promise.reject('failed to save data')
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
//check, 只改不增者之事後偵測: 逐筆n為0表檢查後、寫入前該列被他方刪除(表鎖紀律被破壞), 已寫入之筆無從回復, 仍回報rowNotFound
|
|
1324
|
+
if (optProc.mustExist && hasNoHit(r, null)) {
|
|
1325
|
+
console.log(`${woName}.save row not found after check`)
|
|
1326
|
+
return Promise.reject('rowNotFound')
|
|
1327
|
+
}
|
|
1170
1328
|
}
|
|
1171
1329
|
|
|
1172
1330
|
//僅回傳id避免大量數據時造成負擔
|
|
@@ -1195,6 +1353,31 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1195
1353
|
input = castIdRows(input)
|
|
1196
1354
|
}
|
|
1197
1355
|
|
|
1356
|
+
//exist, 刪除回報未命中(opt.mustExist, R-ONLYMOD-01): 刪除之前檢查全部列皆存在, 任一列不存在(含無主鍵之列)即整批reject 'rowNotFound'而不刪除任何一筆
|
|
1357
|
+
//檢查與刪除非原子, 呼叫端須已持有該表之表鎖(withTabLocks、guardWrite)
|
|
1358
|
+
if (optProc.mustExist) {
|
|
1359
|
+
|
|
1360
|
+
//check, 同批重複主鍵整批拒絕, 訊息列其位置而不回顯主鍵(同R-MIX-01)
|
|
1361
|
+
let failsDup = getItemsDupId(input)
|
|
1362
|
+
if (size(failsDup) > 0) {
|
|
1363
|
+
console.log(`${woName}.del duplicate id`, failsDup)
|
|
1364
|
+
return Promise.reject(getMsgFail('invalid input of del, duplicate id', failsDup))
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
let missing = await hasMissingRow(wo, input)
|
|
1368
|
+
.catch((msg) => {
|
|
1369
|
+
console.log(`${woName}.del exist check catch`, msg)
|
|
1370
|
+
err = 'failed to delete data'
|
|
1371
|
+
})
|
|
1372
|
+
if (isestr(err)) {
|
|
1373
|
+
return Promise.reject(err)
|
|
1374
|
+
}
|
|
1375
|
+
if (missing) {
|
|
1376
|
+
console.log(`${woName}.del row not found`)
|
|
1377
|
+
return Promise.reject('rowNotFound')
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1198
1381
|
//del
|
|
1199
1382
|
r = await wo.del(input)
|
|
1200
1383
|
.catch((msg) => {
|
|
@@ -1209,6 +1392,17 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1209
1392
|
console.log(`${woName}.del fail`, fails)
|
|
1210
1393
|
err = getMsgFail('failed to delete data', fails)
|
|
1211
1394
|
}
|
|
1395
|
+
else if (isOrmFail(r)) {
|
|
1396
|
+
//回傳聚合物件而ok為0(非契約形狀)者無從逐筆列出, 仍視為失敗, 固定字串(R-ORM-RESULT-01)
|
|
1397
|
+
console.log(`${woName}.del fail`, r)
|
|
1398
|
+
err = 'failed to delete data'
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
//check, 刪除回報未命中者之事後偵測: 逐筆nDeleted或n為0表檢查後、刪除前該列被他方刪除(表鎖紀律被破壞), 已刪除之筆無從回復, 仍回報rowNotFound
|
|
1403
|
+
if (!isestr(err) && optProc.mustExist && hasNoHit(r, 'nDeleted')) {
|
|
1404
|
+
console.log(`${woName}.del row not found after check`)
|
|
1405
|
+
err = 'rowNotFound'
|
|
1212
1406
|
}
|
|
1213
1407
|
|
|
1214
1408
|
}
|
|
@@ -1228,6 +1422,12 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1228
1422
|
err = 'failed to delete data'
|
|
1229
1423
|
})
|
|
1230
1424
|
|
|
1425
|
+
//check, delAll契約為聚合物件且ok恆為1(錯誤皆整批reject); ok為0或逐筆陣列任一筆ok為0者(ORM違約, 見isOrmFail)視為失敗, 不可回成功(R-ORM-RESULT-01)
|
|
1426
|
+
if (!isestr(err) && isOrmFail(r)) {
|
|
1427
|
+
console.log(`${woName}.delAll fail`, r)
|
|
1428
|
+
err = 'failed to delete data'
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1231
1431
|
}
|
|
1232
1432
|
else if (mode === 'mix') {
|
|
1233
1433
|
|
|
@@ -1362,7 +1562,7 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1362
1562
|
}
|
|
1363
1563
|
|
|
1364
1564
|
//insert
|
|
1365
|
-
await wo.insert(add)
|
|
1565
|
+
let rsAdd = await wo.insert(add)
|
|
1366
1566
|
.catch((msg) => {
|
|
1367
1567
|
console.log(`${woName}.insert catch`, msg)
|
|
1368
1568
|
err = 'failed to insert data'
|
|
@@ -1373,6 +1573,12 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1373
1573
|
return Promise.reject(err)
|
|
1374
1574
|
}
|
|
1375
1575
|
|
|
1576
|
+
//check, 同單獨insert, 回報失敗者(isOrmFail)視為整批性失敗, 立即reject, 後續步驟不執行
|
|
1577
|
+
if (isOrmFail(rsAdd)) {
|
|
1578
|
+
console.log(`${woName}.insert fail`, rsAdd)
|
|
1579
|
+
return Promise.reject('failed to insert data')
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1376
1582
|
//r
|
|
1377
1583
|
rt = map(add, (v) => {
|
|
1378
1584
|
return { id: v.id } //僅回傳id避免大量數據時造成負擔
|
|
@@ -1401,6 +1607,11 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1401
1607
|
console.log(`${woName}.del fail`, fails)
|
|
1402
1608
|
msgsFail.push(getMsgFail('failed to delete data', fails))
|
|
1403
1609
|
}
|
|
1610
|
+
else if (isOrmFail(rs)) {
|
|
1611
|
+
//回傳聚合物件而ok為0(非契約形狀)者無從逐筆列出, 視為該步驟整批性失敗, 立即reject, 後續步驟不執行(R-ORM-RESULT-01)
|
|
1612
|
+
console.log(`${woName}.del fail`, rs)
|
|
1613
|
+
return Promise.reject('failed to delete data')
|
|
1614
|
+
}
|
|
1404
1615
|
|
|
1405
1616
|
//r
|
|
1406
1617
|
rt = map(del, (v) => {
|
|
@@ -1436,6 +1647,11 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1436
1647
|
console.log(`${woName}.save fail`, fails)
|
|
1437
1648
|
msgsFail.push(getMsgFail('failed to update data', getItemsFail(rs, diffOrig)))
|
|
1438
1649
|
}
|
|
1650
|
+
else if (isOrmFail(rs)) {
|
|
1651
|
+
//同上, 回傳聚合物件而ok為0(非契約形狀)者立即reject(R-ORM-RESULT-01)
|
|
1652
|
+
console.log(`${woName}.save fail`, rs)
|
|
1653
|
+
return Promise.reject('failed to update data')
|
|
1654
|
+
}
|
|
1439
1655
|
|
|
1440
1656
|
//r
|
|
1441
1657
|
rt = map(diff, (v) => {
|
|
@@ -1465,11 +1681,12 @@ async function execMapOrm(ds, woItems, userId, woName, mode, input) {
|
|
|
1465
1681
|
|
|
1466
1682
|
|
|
1467
1683
|
function getMapOrm(ds, woItems) {
|
|
1468
|
-
//每次呼叫各自綁定ds與woItems並回傳新函數, 令同一行程內之多個實例互不干擾;
|
|
1469
|
-
return async (userId, woName, mode, input) => {
|
|
1470
|
-
return mapOrm(ds, woItems, userId, woName, mode, input)
|
|
1684
|
+
//每次呼叫各自綁定ds與woItems並回傳新函數, 令同一行程內之多個實例互不干擾; 回傳函數之參數形狀為(userId, woName, mode, input, opt), 前四個維持, opt為選用(R-ONLYMOD-01)
|
|
1685
|
+
return async (userId, woName, mode, input, opt) => {
|
|
1686
|
+
return mapOrm(ds, woItems, userId, woName, mode, input, opt)
|
|
1471
1687
|
}
|
|
1472
1688
|
}
|
|
1473
1689
|
|
|
1474
1690
|
|
|
1691
|
+
export { getOptProc }
|
|
1475
1692
|
export default getMapOrm
|
package/src/getProc.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import iseobj from 'wsemi/src/iseobj.mjs'
|
|
|
2
2
|
import pmSeries from 'wsemi/src/pmSeries.mjs'
|
|
3
3
|
import isfun from 'wsemi/src/isfun.mjs'
|
|
4
4
|
import checkOptRemoved from './checkOptRemoved.mjs'
|
|
5
|
+
import { getOptProc } from './getMapOrm.mjs'
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
function addFun(fs, f, name, useGuard = false) {
|
|
@@ -28,11 +29,18 @@ function addFun(fs, f, name, useGuard = false) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
async function procOrm(st, userId, woName, mode, input) {
|
|
32
|
+
async function procOrm(st, userId, woName, mode, input, opt) {
|
|
32
33
|
//封裝mapOrm, 依序執行擴充檢查、前處理、ORM操作與後處理, st為該實例之狀態
|
|
33
34
|
//不做授權: 不檢查使用者亦不依使用者過濾數據, userId只供寫入稽核欄位與交給擴充函數; 開放給前端時須由開放處負責授權
|
|
34
35
|
//st.mapOrm已於getProc建立st時驗證為函數, 且st不外露, 故此處不再檢查
|
|
35
36
|
|
|
37
|
+
//opt(第5參數: mustExist, R-ONLYMOD-01; keepUserId, R-KEEPUSER-01)先於任何擴充函數驗證: 無效之opt以固定字串拒收, 不得先執行擴充函數(其可能有寫入等副作用, 且check拋錯會遮蔽固定拒收字串);
|
|
38
|
+
//mapOrm內仍會再驗(直接使用getMapOrm者), 結果相同; opt不交給擴充函數(其參數物件形狀維持)
|
|
39
|
+
let optProc = getOptProc(mode, opt)
|
|
40
|
+
if (optProc.err !== null) {
|
|
41
|
+
return Promise.reject(optProc.err)
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
//funChecks, 擴充檢查, 提供給各 mOrm[OOO].mjs 檢查
|
|
37
45
|
await pmSeries(st.funChecks, (v) => {
|
|
38
46
|
return v({ woName, userId, mode, input })
|
|
@@ -50,7 +58,8 @@ async function procOrm(st, userId, woName, mode, input) {
|
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
//output
|
|
53
|
-
|
|
61
|
+
//opt(第5參數, R-ONLYMOD-01、R-KEEPUSER-01)交給mapOrm使用
|
|
62
|
+
let output = await st.mapOrm(userId, woName, mode, input, opt)
|
|
54
63
|
|
|
55
64
|
//funPostProcessing, 擴充後處理output, 提供給各 mOrm[OOO].mjs 後處理
|
|
56
65
|
//逐一呼叫, 各後處理皆收新建之參數物件, output為前一個之回傳值, 回傳值成為新output; 迭代前先取快照
|
|
@@ -96,8 +105,8 @@ function getProc(mapOrm, opt = {}) {
|
|
|
96
105
|
//由外部添加擴充後置處理函數
|
|
97
106
|
addFun(st.funPostProcessing, f, 'addFunPostProcessing')
|
|
98
107
|
},
|
|
99
|
-
procOrm: async (userId, woName, mode, input) => {
|
|
100
|
-
return procOrm(st, userId, woName, mode, input)
|
|
108
|
+
procOrm: async (userId, woName, mode, input, opt) => {
|
|
109
|
+
return procOrm(st, userId, woName, mode, input, opt)
|
|
101
110
|
},
|
|
102
111
|
}
|
|
103
112
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import size from 'lodash-es/size.js'
|
|
2
|
+
import getItemsFail from 'w-data-collector/src/getItemsFail.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 判斷ORM寫入類函數(insert、save、del、delAll)之回傳結果是否回報失敗
|
|
7
|
+
*
|
|
8
|
+
* 依w-orm系列規格,insert與delAll回傳聚合物件且ok恆為1,save與del回傳逐筆陣列,錯誤皆整批reject;單筆失敗者(save、del)於該筆以ok為0回報。
|
|
9
|
+
* 本函數不論函數與形狀一律判讀:回傳為聚合物件(非null、非陣列之物件)而ok為0者(ORM違約或他種後端)視為失敗;
|
|
10
|
+
* 回傳為逐筆陣列而任一筆ok為0者(同getItemsFail之判讀)視為失敗。
|
|
11
|
+
* 其餘(undefined、null、無ok鍵之物件、空陣列、元素皆ok非0或非物件之陣列、ok為1含略過既有主鍵或未命中之n為0)不視為失敗,維持舊行為。
|
|
12
|
+
* 讀取函數(select)之回傳為數據列,列內欄位可能名為ok,不得以本函數判讀。
|
|
13
|
+
* 供procOrm之寫入類各模式、mix之各步驟、備份還原與測試數據寫入共用。
|
|
14
|
+
*
|
|
15
|
+
* @param {*} r 輸入ORM寫入類函數之回傳結果
|
|
16
|
+
* @returns {Boolean} 回傳是否回報失敗
|
|
17
|
+
*/
|
|
18
|
+
function isOrmFail(r) {
|
|
19
|
+
|
|
20
|
+
//check, 逐筆陣列
|
|
21
|
+
if (Array.isArray(r)) {
|
|
22
|
+
return size(getItemsFail(r, [])) > 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//check, 聚合物件
|
|
26
|
+
return r !== null && typeof r === 'object' && r.ok === 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export default isOrmFail
|