w-serv-orm 1.1.7 → 1.1.8

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/src/getMapOrm.mjs CHANGED
@@ -4,54 +4,98 @@ import map from 'lodash-es/map.js'
4
4
  import size from 'lodash-es/size.js'
5
5
  import take from 'lodash-es/take.js'
6
6
  import join from 'lodash-es/join.js'
7
+ import truncate from 'lodash-es/truncate.js'
7
8
  import nowms2str from 'wsemi/src/nowms2str.mjs'
9
+ import genIDSeq from 'wsemi/src/genIDSeq.mjs'
8
10
  import isarr from 'wsemi/src/isarr.mjs'
9
11
  import iseobj from 'wsemi/src/iseobj.mjs'
10
12
  import isestr from 'wsemi/src/isestr.mjs'
11
13
  import isnum from 'wsemi/src/isnum.mjs'
12
14
  import clearXSS from 'wsemi/src/clearXSS.mjs'
13
- import getItemsFail from './getItemsFail.mjs'
14
-
15
-
16
- let _ds = null
17
- let _woItems = null
15
+ import getItemsFail from 'w-data-collector/src/getItemsFail.mjs'
18
16
 
19
17
 
20
18
  function getMsgFail(msg, fails) {
21
19
  //前端訊息僅列出失敗筆, 錯誤原文另由console.log輸出, 與既有整批錯誤之處理一致
22
20
  //有效主鍵(非空字串或數字)列為id[...], 否則列其於輸入內之位置, 失敗筆數過多時僅列前nShow筆並附總筆數
21
+ //主鍵超過nIdMax字元者截斷顯示(LMDB逐筆失敗常因主鍵超長, 故兩者常同時出現), log仍保留全文
23
22
 
24
- //nShow
23
+ //nShow, nIdMax
25
24
  let nShow = 10
25
+ let nIdMax = 100
26
26
 
27
27
  //ss
28
28
  let ss = map(take(fails, nShow), (v) => {
29
29
  if (isestr(v.id) || isnum(v.id)) {
30
- return `id[${v.id}]`
30
+ return `id[${truncate(String(v.id), { length: nIdMax, omission: '…' })}]`
31
31
  }
32
- return `第${v.k + 1}筆`
32
+ return `data[${v.k}]`
33
33
  })
34
34
 
35
35
  //c
36
36
  let c = `${msg}: ${join(ss, ', ')}`
37
37
  if (size(fails) > nShow) {
38
- c += `等共${size(fails)}筆`
38
+ c += `, ... (${size(fails)} in total)`
39
39
  }
40
40
 
41
41
  return c
42
42
  }
43
43
 
44
44
 
45
- function adjustData(woName, userId, data, mode = 'save') { //mode: 'insert', 'save', 'insert+save'
45
+ function isPkIdString(dsTab, keys) {
46
+ //資料表之主鍵欄是否為id且可容納字串型之補值, 供adjustData判斷是否補主鍵
47
+
48
+ //check, 須有id欄; 有settings者另須標為pk
49
+ if (keys.indexOf('id') < 0) {
50
+ return false
51
+ }
52
+ let settings = get(dsTab, 'settings', null)
53
+ if (iseobj(settings) && get(settings, 'id.pk') !== true) {
54
+ return false
55
+ }
56
+
57
+ //check, 型別已知者須為字串型, 與w-orm-reladb可自動補值之主鍵欄型別一致
58
+ let type = get(settings, 'id.type', '') || get(dsTab, 'kpType.id', '')
59
+ if (isestr(type) && ['STRING', 'TEXT', 'CHAR', 'UUID', 'CITEXT'].indexOf(type.toUpperCase()) < 0) {
60
+ return false
61
+ }
62
+
63
+ return true
64
+ }
65
+
66
+
67
+ function getItemsIdXSS(data) {
68
+ //輸入數據之主鍵(非空字串)會被clearXSS改寫者, 回傳[{k,id}], k為其於輸入內之位置
69
+ //寫入路徑之欄位皆經clearXSS而select與del不經過, 主鍵被改寫後以原主鍵查詢與刪除皆找不到, 故呼叫端須於寫入前整批拒絕
70
+ let rs = []
71
+ each(isarr(data) ? data : [data], (v, k) => {
72
+ let id = get(v, 'id')
73
+ if (isestr(id) && clearXSS(id) !== id) {
74
+ rs.push({ k, id })
75
+ }
76
+ })
77
+ return rs
78
+ }
79
+
80
+
81
+ function getMsgIdXSS(msg, fails) {
82
+ //主鍵含HTML標記, 訊息只列位置不回顯主鍵, 主鍵原文另由console.log輸出
83
+ return getMsgFail(`${msg}, id contains HTML markup`, map(fails, (v) => {
84
+ return { k: v.k }
85
+ }))
86
+ }
87
+
88
+
89
+ function adjustData(ds, woName, userId, data, mode = 'save') { //mode: 'insert', 'save', 'insert+save'
46
90
 
47
91
  //check
48
- if (!iseobj(_ds)) {
49
- console.log('ds', _ds)
92
+ if (!iseobj(ds)) {
93
+ console.log('ds', ds)
50
94
  throw new Error('invalid ds')
51
95
  }
52
96
 
53
97
  //keys
54
- let keys = _ds[woName].keys
98
+ let keys = ds[woName].keys
55
99
 
56
100
  //one
57
101
  let one = false
@@ -128,6 +172,17 @@ function adjustData(woName, userId, data, mode = 'save') { //mode: 'insert', 'sa
128
172
  })
129
173
  }
130
174
 
175
+ //id, 未提供有效主鍵者由本套件補值, 令回傳之主鍵與實際寫入者一致, 補值器同各ORM之genIDSeq
176
+ //有效主鍵之判定同w-orm系列: 非空字串或數字(reladb、mdb視數字為有效)
177
+ //補值寫在上方之複本, 不改呼叫端原物件; 無欄位之列不補, 保留ORM視空物件為無效輸入之行為
178
+ 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
+ })
184
+ }
185
+
131
186
  //復原data型態, 若原本data為物件則取第0個回傳
132
187
  data = one ? data[0] : data
133
188
 
@@ -135,22 +190,22 @@ function adjustData(woName, userId, data, mode = 'save') { //mode: 'insert', 'sa
135
190
  }
136
191
 
137
192
 
138
- async function mapOrm(userId, woName, mode, input) {
193
+ async function mapOrm(ds, woItems, userId, woName, mode, input) {
139
194
  //由woName自動綁定至woItems內的ORM物件
140
195
  let err = null
141
196
  let r
142
197
 
143
198
  //check
144
- if (!iseobj(_woItems)) {
145
- return Promise.reject(`無有效woItems`)
199
+ if (!iseobj(woItems)) {
200
+ return Promise.reject(`invalid woItems`)
146
201
  }
147
202
 
148
203
  //wo
149
- let wo = get(_woItems, woName, null)
204
+ let wo = get(woItems, woName, null)
150
205
 
151
206
  //check
152
207
  if (wo === null) {
153
- return Promise.reject(`ORM找不到表名稱: ${woName}`)
208
+ return Promise.reject(`can not find table[${woName}] in ORM`)
154
209
  }
155
210
 
156
211
  if (mode === 'select') {
@@ -159,7 +214,7 @@ async function mapOrm(userId, woName, mode, input) {
159
214
  r = await wo.select(input)
160
215
  .catch((msg) => {
161
216
  console.log(`${woName}.select catch`, msg)
162
- err = '取得資料失敗'
217
+ err = 'failed to select data'
163
218
  })
164
219
 
165
220
  //check
@@ -170,13 +225,23 @@ async function mapOrm(userId, woName, mode, input) {
170
225
  }
171
226
  else if (mode === 'insert' || mode === 'save') {
172
227
 
228
+ //inputOrig, 呼叫端原輸入, 逐筆失敗之訊息以呼叫端視角列出(本套件補出之主鍵只寫於複本, reject時呼叫端未曾取得)
229
+ let inputOrig = input
230
+
231
+ //check, 主鍵會被clearXSS改寫者整批拒絕, 不寫入任何一筆
232
+ let failsIdXSS = getItemsIdXSS(input)
233
+ if (size(failsIdXSS) > 0) {
234
+ 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))
236
+ }
237
+
173
238
  //adjustData
174
239
  try {
175
- input = adjustData(woName, userId, input, mode)
240
+ input = adjustData(ds, woName, userId, input, mode)
176
241
  }
177
242
  catch (e) {
178
- console.log(`adjustData catch`, e.toString())
179
- err = '調整資料失敗'
243
+ console.log(`${woName}.adjustData catch`, e.toString())
244
+ err = 'failed to adjust data'
180
245
  return Promise.reject(err)
181
246
  }
182
247
 
@@ -184,15 +249,15 @@ async function mapOrm(userId, woName, mode, input) {
184
249
  if (mode === 'insert') {
185
250
  r = await wo.insert(input)
186
251
  .catch((msg) => {
187
- console.log(`${woName}.save catch`, msg)
188
- err = '新增資料失敗'
252
+ console.log(`${woName}.insert catch`, msg)
253
+ err = 'failed to insert data'
189
254
  })
190
255
  }
191
256
  else if (mode === 'save') {
192
257
  r = await wo.save(input)
193
258
  .catch((msg) => {
194
259
  console.log(`${woName}.save catch`, msg)
195
- err = '儲存資料失敗'
260
+ err = 'failed to save data'
196
261
  })
197
262
  }
198
263
 
@@ -207,7 +272,7 @@ async function mapOrm(userId, woName, mode, input) {
207
272
  let fails = getItemsFail(r, input)
208
273
  if (size(fails) > 0) {
209
274
  console.log(`${woName}.save fail`, fails)
210
- return Promise.reject(getMsgFail('儲存資料失敗', fails))
275
+ return Promise.reject(getMsgFail('failed to save data', getItemsFail(r, inputOrig)))
211
276
  }
212
277
  }
213
278
 
@@ -229,7 +294,7 @@ async function mapOrm(userId, woName, mode, input) {
229
294
  r = await wo.del(input)
230
295
  .catch((msg) => {
231
296
  console.log(`${woName}.del catch`, msg)
232
- err = '刪除資料失敗'
297
+ err = 'failed to delete data'
233
298
  })
234
299
 
235
300
  //check, 單筆失敗時整批仍resolve並以該筆ok為0回報, 須逐筆檢查
@@ -237,7 +302,7 @@ async function mapOrm(userId, woName, mode, input) {
237
302
  let fails = getItemsFail(r, input)
238
303
  if (size(fails) > 0) {
239
304
  console.log(`${woName}.del fail`, fails)
240
- err = getMsgFail('刪除資料失敗', fails)
305
+ err = getMsgFail('failed to delete data', fails)
241
306
  }
242
307
  }
243
308
 
@@ -248,7 +313,7 @@ async function mapOrm(userId, woName, mode, input) {
248
313
  r = await wo.delAll(input)
249
314
  .catch((msg) => {
250
315
  console.log(`${woName}.delAll catch`, msg)
251
- err = '刪除資料失敗'
316
+ err = 'failed to delete data'
252
317
  })
253
318
 
254
319
  }
@@ -256,7 +321,20 @@ async function mapOrm(userId, woName, mode, input) {
256
321
 
257
322
  //check
258
323
  if (!iseobj(input)) {
259
- return Promise.reject('mix僅支援輸入ltdtDiffByKey結果物件')
324
+ return Promise.reject('invalid input of mix, need the result object of ltdtDiffByKey')
325
+ }
326
+
327
+ //check, 新增與修改之主鍵會被clearXSS改寫者, 於任何步驟寫入之前整批拒絕
328
+ let msgsIdXSS = []
329
+ for (let [key, msg] of [['add', 'failed to insert data'], ['diff', 'failed to update data']]) {
330
+ let fails = getItemsIdXSS(get(input, key, []))
331
+ if (size(fails) > 0) {
332
+ console.log(`${woName}.mix ${key} id changed by clearXSS`, fails)
333
+ msgsIdXSS.push(getMsgIdXSS(msg, fails))
334
+ }
335
+ }
336
+ if (size(msgsIdXSS) > 0) {
337
+ return Promise.reject(join(msgsIdXSS, '; '))
260
338
  }
261
339
 
262
340
  //rt,r
@@ -271,19 +349,19 @@ async function mapOrm(userId, woName, mode, input) {
271
349
 
272
350
  //adjustData
273
351
  try {
274
- add = adjustData(woName, userId, add, 'insert+save')
352
+ add = adjustData(ds, woName, userId, add, 'insert+save')
275
353
  }
276
354
  catch (e) {
277
- console.log(`adjustData catch`, e.toString())
278
- err = '調整資料失敗'
355
+ console.log(`${woName}.adjustData catch`, e.toString())
356
+ err = 'failed to adjust data'
279
357
  return Promise.reject(err)
280
358
  }
281
359
 
282
360
  //insert
283
361
  await wo.insert(add)
284
362
  .catch((msg) => {
285
- console.log(`${woName}.save catch`, msg)
286
- err = '新增資料失敗'
363
+ console.log(`${woName}.insert catch`, msg)
364
+ err = 'failed to insert data'
287
365
  })
288
366
 
289
367
  //check
@@ -304,8 +382,8 @@ async function mapOrm(userId, woName, mode, input) {
304
382
  //del
305
383
  let rs = await wo.del(del)
306
384
  .catch((msg) => {
307
- console.log(`${woName}.save catch`, msg)
308
- err = '刪除資料失敗'
385
+ console.log(`${woName}.del catch`, msg)
386
+ err = 'failed to delete data'
309
387
  })
310
388
 
311
389
  //check
@@ -317,7 +395,7 @@ async function mapOrm(userId, woName, mode, input) {
317
395
  let fails = getItemsFail(rs, del)
318
396
  if (size(fails) > 0) {
319
397
  console.log(`${woName}.del fail`, fails)
320
- msgsFail.push(getMsgFail('刪除資料失敗', fails))
398
+ msgsFail.push(getMsgFail('failed to delete data', fails))
321
399
  }
322
400
 
323
401
  //r
@@ -330,13 +408,16 @@ async function mapOrm(userId, woName, mode, input) {
330
408
  let diff = get(input, 'diff', [])
331
409
  if (size(diff) > 0) {
332
410
 
411
+ //diffOrig, 呼叫端原輸入, 逐筆失敗之訊息以呼叫端視角列出
412
+ let diffOrig = diff
413
+
333
414
  //adjustData
334
415
  try {
335
- diff = adjustData(woName, userId, diff)
416
+ diff = adjustData(ds, woName, userId, diff)
336
417
  }
337
418
  catch (e) {
338
- console.log(`adjustData catch`, e.toString())
339
- err = '調整資料失敗'
419
+ console.log(`${woName}.adjustData catch`, e.toString())
420
+ err = 'failed to adjust data'
340
421
  return Promise.reject(err)
341
422
  }
342
423
 
@@ -344,7 +425,7 @@ async function mapOrm(userId, woName, mode, input) {
344
425
  let rs = await wo.save(diff)
345
426
  .catch((msg) => {
346
427
  console.log(`${woName}.save catch`, msg)
347
- err = '變更資料失敗'
428
+ err = 'failed to update data'
348
429
  })
349
430
 
350
431
  //check
@@ -356,7 +437,7 @@ async function mapOrm(userId, woName, mode, input) {
356
437
  let fails = getItemsFail(rs, diff)
357
438
  if (size(fails) > 0) {
358
439
  console.log(`${woName}.save fail`, fails)
359
- msgsFail.push(getMsgFail('變更資料失敗', fails))
440
+ msgsFail.push(getMsgFail('failed to update data', getItemsFail(rs, diffOrig)))
360
441
  }
361
442
 
362
443
  //r
@@ -387,9 +468,10 @@ async function mapOrm(userId, woName, mode, input) {
387
468
 
388
469
 
389
470
  function getMapOrm(ds, woItems) {
390
- _ds = ds
391
- _woItems = woItems
392
- return mapOrm
471
+ //每次呼叫各自綁定ds與woItems並回傳新函數, 令同一行程內之多個實例互不干擾; 回傳函數之參數形狀維持(userId, woName, mode, input)
472
+ return async (userId, woName, mode, input) => {
473
+ return mapOrm(ds, woItems, userId, woName, mode, input)
474
+ }
393
475
  }
394
476
 
395
477
 
package/src/getProc.mjs CHANGED
@@ -1,88 +1,72 @@
1
1
  import get from 'lodash-es/get.js'
2
- import size from 'lodash-es/size.js'
3
2
  import filter from 'lodash-es/filter.js'
4
3
  import isarr from 'wsemi/src/isarr.mjs'
5
4
  import isestr from 'wsemi/src/isestr.mjs'
5
+ import iseobj from 'wsemi/src/iseobj.mjs'
6
6
  import haskey from 'wsemi/src/haskey.mjs'
7
7
  import pmSeries from 'wsemi/src/pmSeries.mjs'
8
- import pmChain from 'wsemi/src/pmChain.mjs'
9
8
  import isfun from 'wsemi/src/isfun.mjs'
10
9
  import isbol from 'wsemi/src/isbol.mjs'
11
10
 
12
11
 
13
- let _funChecks = []
14
- let _funPreProcessing = []
15
- let _funPostProcessing = []
16
- let _mapOrm = null
17
- let _getUserById = null
18
- let useCheckUser = true
19
- let useExcludeWhenNotAdmin = true
12
+ function addFun(fs, f, name, useGuard = false) {
13
+ //由外部添加擴充函數至該實例之陣列
14
+ //延後1ms生效係沿用既有之生效時序: 原為避開模組層陣列於循環引用時之「Cannot access before initialization」,
15
+ //改為各實例狀態後該原因已不成立, 惟使用端之啟動流程可能依賴此時序, 故保留
20
16
 
17
+ //check, 非函數於註冊時即拋錯; useGuard(check用)另推入恆reject之守門函數, 令拋錯被呼叫端攔下時該實例仍維持fail-closed
18
+ //註冊時拋錯屬建構期錯誤故為Error; 守門函數於procOrm內reject, 與procOrm其餘拒絕一致為字串
19
+ if (!isfun(f)) {
20
+ let msg = `${name} needs a function, but got ${typeof f}`
21
+ if (useGuard) {
22
+ fs.push(() => {
23
+ return Promise.reject(msg)
24
+ })
25
+ }
26
+ throw new Error(msg)
27
+ }
21
28
 
22
- function addFunCheck(f) {
23
- //由外部添加擴充檢查函數
24
-
25
- setTimeout(() => { //因互相引用的關係會出現「Cannot access '_funChecks' before initialization」, 故通過timer脫勾成為動態引用, 避開執行mInitialTestData時會先編譯與偵測問題
26
- _funChecks.push(f)
27
- }, 1)
28
-
29
- }
30
-
31
-
32
- function addFunPreProcessing(f) {
33
- //由外部添加擴充前處理函數
34
-
35
- setTimeout(() => { //因互相引用的關係會出現「Cannot access '_funChecks' before initialization」, 故通過timer脫勾成為動態引用, 避開執行mInitialTestData時會先編譯與偵測問題
36
- _funPreProcessing.push(f)
37
- }, 1)
38
-
39
- }
40
-
41
-
42
- function addFunPostProcessing(f) {
43
- //由外部添加擴充後置處理函數
44
-
45
- setTimeout(() => { //因互相引用的關係會出現「Cannot access '_funChecks' before initialization」, 故通過timer脫勾成為動態引用, 避開執行mInitialTestData時會先編譯與偵測問題
46
- _funPostProcessing.push(f)
29
+ setTimeout(() => {
30
+ fs.push(f)
47
31
  }, 1)
48
32
 
49
33
  }
50
34
 
51
35
 
52
- async function procOrm(userId, woName, mode, input) {
53
- //封裝mapOrm, 添加檢查userId功能
36
+ async function procOrm(st, userId, woName, mode, input) {
37
+ //封裝mapOrm, 添加檢查userId功能, st為該實例之狀態
54
38
 
55
39
  //check
56
- if (!isfun(_mapOrm)) {
40
+ if (!isfun(st.mapOrm)) {
57
41
  return Promise.reject(`invalid mapOrm`)
58
42
  }
59
43
 
60
44
  //check
61
- if (useCheckUser && !isestr(userId)) {
45
+ if (st.useCheckUser && !isestr(userId)) {
62
46
  console.log('userId', userId)
63
- console.log('找不到使用者主鍵')
64
- return Promise.reject(`找不到使用者主鍵`)
47
+ console.log('invalid userId')
48
+ return Promise.reject(`invalid userId`)
65
49
  }
66
50
 
67
51
  //isAdmin
68
52
  let isAdmin = 'n'
69
53
 
70
54
  //useCheckUser
71
- if (useCheckUser) {
55
+ if (st.useCheckUser) {
72
56
 
73
57
  //check
74
- if (!isfun(_getUserById)) {
58
+ if (!isfun(st.getUserById)) {
75
59
  return Promise.reject(`invalid getUserById`)
76
60
  }
77
61
 
78
62
  //oSelf
79
- let oSelf = await _getUserById(userId)
63
+ let oSelf = await st.getUserById(userId)
80
64
 
81
65
  //check
82
66
  if (!oSelf) {
83
67
  console.log('userId', userId)
84
- console.log('找不到使用者資訊')
85
- return Promise.reject(`找不到使用者資訊`)
68
+ console.log('can not find user')
69
+ return Promise.reject(`can not find user`)
86
70
  }
87
71
 
88
72
  //isAdmin
@@ -101,29 +85,33 @@ async function procOrm(userId, woName, mode, input) {
101
85
  if (isActive !== 'y') {
102
86
  console.log('userId', userId)
103
87
  console.log('oSelf', oSelf)
104
- console.log('使用者被停權或無有效使用者資訊')
105
- return Promise.reject(`使用者被停權或無有效使用者資訊`)
88
+ console.log('user is inactive or invalid')
89
+ return Promise.reject(`user is inactive or invalid`)
106
90
  }
107
91
 
108
92
  }
109
93
 
110
- //_funChecks, 擴充檢查, 提供給各 mOrm[OOO].mjs 檢查
111
- await pmSeries(_funChecks, (v) => {
94
+ //funChecks, 擴充檢查, 提供給各 mOrm[OOO].mjs 檢查
95
+ await pmSeries(st.funChecks, (v) => {
112
96
  return v({ woName, userId, isAdmin, mode, input })
113
97
  })
114
98
 
115
- //_funPreProcessing, 擴充前處理input, 提供給各 mOrm[OOO].mjs 前處理
116
- if (size(_funPreProcessing) > 0) {
117
- let paramsIn = { woName, userId, isAdmin, mode, input } //將input與其他資訊一起封裝成params
118
- let paramsOut = await pmChain(_funPreProcessing, paramsIn) //各非同步函數拿到輸入處理完皆輸出一樣結構數據params, 使各非同步函數都可拿到非input的重要資訊
119
- input = paramsOut.input //由params提取input出來
99
+ //funPreProcessing, 擴充前處理input, 提供給各 mOrm[OOO].mjs 前處理
100
+ //逐一呼叫, 各前處理皆收新建之參數物件, input為前一個回傳之input; 回傳須為含input之物件, 否則reject, 避免input成為undefined而令select回全表
101
+ //迭代前先取快照, 呼叫進行中註冊者不套用於本次呼叫
102
+ for (let f of [...st.funPreProcessing]) {
103
+ let paramsOut = await f({ woName, userId, isAdmin, mode, input })
104
+ if (!iseobj(paramsOut) || paramsOut.input === undefined) {
105
+ return Promise.reject(`invalid return of pre-processing, need an object with input`)
106
+ }
107
+ input = paramsOut.input
120
108
  }
121
109
 
122
110
  //output
123
- let output = await _mapOrm(userId, woName, mode, input)
111
+ let output = await st.mapOrm(userId, woName, mode, input)
124
112
 
125
113
  //useExcludeWhenNotAdmin, 非管理者則要過濾掉已刪除之數據
126
- if (useExcludeWhenNotAdmin && isAdmin === 'n') {
114
+ if (st.useExcludeWhenNotAdmin && isAdmin === 'n') {
127
115
  if (mode === 'select' && isarr(output)) { //有可能為錯誤物件
128
116
  output = filter(output, (v) => {
129
117
  if (haskey(v, 'isActive')) { //針對有isActive屬性
@@ -134,9 +122,10 @@ async function procOrm(userId, woName, mode, input) {
134
122
  }
135
123
  }
136
124
 
137
- //_funPostProcessing, 擴充後處理output, 提供給各 mOrm[OOO].mjs 後處理
138
- if (size(_funPostProcessing) > 0) {
139
- output = await pmChain(_funPostProcessing, { woName, userId, isAdmin, mode, input, output })
125
+ //funPostProcessing, 擴充後處理output, 提供給各 mOrm[OOO].mjs 後處理
126
+ //逐一呼叫, 各後處理皆收新建之參數物件, output為前一個之回傳值, 回傳值成為新output; 迭代前先取快照
127
+ for (let f of [...st.funPostProcessing]) {
128
+ output = await f({ woName, userId, isAdmin, mode, input, output })
140
129
  }
141
130
 
142
131
  return output
@@ -150,29 +139,49 @@ function getProc(mapOrm, opt = {}) {
150
139
  return Promise.reject(`invalid mapOrm`)
151
140
  }
152
141
 
153
- //save _mapOrm
154
- _mapOrm = mapOrm
155
-
156
142
  //useCheckUser
157
- useCheckUser = get(opt, 'useCheckUser')
143
+ let useCheckUser = get(opt, 'useCheckUser')
158
144
  if (!isbol(useCheckUser)) {
159
145
  useCheckUser = true
160
146
  }
161
147
 
162
- //save _getUserById, 不事先檢查, 因若useCheckUser=false可允許不給
163
- _getUserById = get(opt, 'getUserById')
148
+ //getUserById, 不事先檢查, 因若useCheckUser=false可允許不給
149
+ let getUserById = get(opt, 'getUserById')
164
150
 
165
151
  //useExcludeWhenNotAdmin
166
- useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin')
152
+ let useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin')
167
153
  if (!isbol(useExcludeWhenNotAdmin)) {
168
154
  useExcludeWhenNotAdmin = true
169
155
  }
170
156
 
157
+ //st, 各實例之狀態, 每次呼叫各自建立, 令同一行程內之多個實例互不干擾
158
+ let st = {
159
+ funChecks: [],
160
+ funPreProcessing: [],
161
+ funPostProcessing: [],
162
+ mapOrm,
163
+ getUserById,
164
+ useCheckUser,
165
+ useExcludeWhenNotAdmin,
166
+ }
167
+
168
+ //回傳函數綁定st而不依賴this, 使用端可解構取用並當裸函數傳遞
171
169
  return {
172
- addFunCheck,
173
- addFunPreProcessing,
174
- addFunPostProcessing,
175
- procOrm,
170
+ addFunCheck: (f) => {
171
+ //由外部添加擴充檢查函數
172
+ addFun(st.funChecks, f, 'addFunCheck', true)
173
+ },
174
+ addFunPreProcessing: (f) => {
175
+ //由外部添加擴充前處理函數
176
+ addFun(st.funPreProcessing, f, 'addFunPreProcessing')
177
+ },
178
+ addFunPostProcessing: (f) => {
179
+ //由外部添加擴充後置處理函數
180
+ addFun(st.funPostProcessing, f, 'addFunPostProcessing')
181
+ },
182
+ procOrm: async (userId, woName, mode, input) => {
183
+ return procOrm(st, userId, woName, mode, input)
184
+ },
176
185
  }
177
186
  }
178
187
 
@@ -76,7 +76,7 @@ function getSettings(opt = {}) {
76
76
  }
77
77
  catch (err) {
78
78
  console.log(err)
79
- throw new Error('伺服器讀取設定檔失敗')
79
+ throw new Error('failed to read settings file')
80
80
  }
81
81
 
82
82
  return st
@@ -46,7 +46,7 @@ describe('procOrm接w-orm-lowdb', function() {
46
46
  it('A02 del含無效主鍵', async function() {
47
47
  //R02: 逐筆ok為0即reject並列出失敗主鍵; R10: 單筆失敗不中斷整批, 有效筆已刪除
48
48
  let err = await getReject(proc('del', [{ id: 'a1' }, { id: 123 }]))
49
- assert.strict.equal(err, '刪除資料失敗: id[123]')
49
+ assert.strict.equal(err, 'failed to delete data: id[123]')
50
50
  let rs = await proc('select', { id: 'a1' })
51
51
  assert.strict.equal(rs.length, 0)
52
52
  })
@@ -54,7 +54,7 @@ describe('procOrm接w-orm-lowdb', function() {
54
54
  it('A03 mix之del含無效主鍵', async function() {
55
55
  //R03: del之逐筆失敗不中斷diff, diff已寫入, 最後reject列出del之失敗
56
56
  let err = await getReject(proc('mix', { del: [{ id: 456 }], diff: [{ id: 'a2', isActive: 'y' }] }))
57
- assert.strict.equal(err, '刪除資料失敗: id[456]')
57
+ assert.strict.equal(err, 'failed to delete data: id[456]')
58
58
  let rs = await proc('select', { id: 'a2' })
59
59
  assert.strict.equal(rs.length, 1)
60
60
  })
@@ -65,4 +65,29 @@ describe('procOrm接w-orm-lowdb', function() {
65
65
  assert.strict.deepEqual(r, [{ n: 0, nDeleted: 0, ok: 1 }])
66
66
  })
67
67
 
68
+ it('A05 save未帶主鍵時回傳之id可於資料庫查得', async function() {
69
+ //D1: 本套件補值並回傳, 與實際寫入之主鍵一致
70
+ let r = await proc('save', [{ isActive: 'y' }])
71
+ assert.ok(typeof r[0].id === 'string' && r[0].id.length > 0)
72
+ let rs = await proc('select', { id: r[0].id })
73
+ assert.strict.equal(rs.length, 1)
74
+ })
75
+
76
+ it('A06 主鍵為數學式者可依原主鍵查詢與刪除', async function() {
77
+ //wsemi 1.9.13起數學式不被clearXSS改寫, 寫入之主鍵即呼叫端所持者(1.9.12時會存成a &lt; b而查不到、刪不掉)
78
+ let r = await proc('save', [{ id: 'a < b', isActive: 'y' }])
79
+ assert.strict.deepEqual(r, [{ id: 'a < b' }])
80
+ let rs = await proc('select', { id: 'a < b' })
81
+ assert.strict.equal(rs.length, 1)
82
+ let rd = await proc('del', [{ id: 'a < b' }])
83
+ assert.strict.deepEqual(rd, [{ n: 1, nDeleted: 1, ok: 1 }])
84
+ })
85
+
86
+ it('A07 主鍵會被改寫者整批拒絕且未寫入任何一筆', async function() {
87
+ let err = await getReject(proc('save', [{ id: 'ok-2', isActive: 'y' }, { id: 'a<b', isActive: 'y' }]))
88
+ assert.strict.equal(err, 'failed to save data, id contains HTML markup: data[1]')
89
+ let rs = await proc('select', {})
90
+ assert.ok(!rs.some((v) => ['ok-2', 'a<b', 'a&lt;b'].includes(v.id)), JSON.stringify(rs))
91
+ })
92
+
68
93
  })