w-web-perm 1.1.8 → 1.1.9

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.
Files changed (51) hide show
  1. package/README.md +14 -4
  2. package/dist/index.tmp +1 -1
  3. package/dist/js/app-legacy.ecb7ce08.js +1 -0
  4. package/dist/js/app.ecb7ce08.js +1 -0
  5. package/dist/js/{chunk-vendors-legacy.00f159e8.js → chunk-vendors-legacy.da785a99.js} +3 -3
  6. package/dist/js/{chunk-vendors.00f159e8.js → chunk-vendors.da785a99.js} +3 -3
  7. package/dist/w-web-perm.umd.js +2 -2
  8. package/dist/w-web-perm.umd.js.map +1 -1
  9. package/docs/WWebPerm.html +78 -128
  10. package/docs/global.html +1280 -39
  11. package/docs/index.html +2 -2
  12. package/docs/server_WWebPerm.mjs.html +1247 -1196
  13. package/docs/server_dataChannel.mjs.html +131 -0
  14. package/docs/server_lockSave.mjs.html +2 -2
  15. package/docs/server_maskLog.mjs.html +2 -2
  16. package/docs/server_procCore.mjs.html +53 -11
  17. package/docs/server_staLogs_resolveShellWorker.mjs.html +180 -0
  18. package/docs/server_validateRows.mjs.html +184 -0
  19. package/docs/server_wrapInjected.mjs.html +2 -2
  20. package/docs/src_fetchJson.mjs.html +2 -2
  21. package/docs/src_js_legendFit.mjs.html +2 -2
  22. package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
  23. package/docs/src_plugins_submitGuard.mjs.html +2 -2
  24. package/g_backupDb.mjs +4 -0
  25. package/g_initialData.mjs +1 -0
  26. package/g_initialTestData.mjs +1 -0
  27. package/g_mOrm.mjs +2 -7
  28. package/g_provideGrups.mjs +1 -0
  29. package/g_providePemis.mjs +1 -0
  30. package/g_provideTargets.mjs +1 -0
  31. package/g_provideUsers.mjs +1 -0
  32. package/package.json +10 -10
  33. package/server/WWebPerm.mjs +1245 -1194
  34. package/server/dataChannel.mjs +59 -0
  35. package/server/procCore.mjs +51 -9
  36. package/server/procLang.mjs +13 -3
  37. package/server/staLogs/resolveShellWorker.mjs +108 -0
  38. package/server/staLogs/staLogsCore.callWorker.mjs +29 -9
  39. package/server/validateRows.mjs +112 -0
  40. package/src/components/LayoutContentGrups.vue +8 -8
  41. package/src/components/LayoutContentPemis.vue +8 -8
  42. package/src/components/LayoutContentTargets.vue +2 -2
  43. package/src/components/LayoutContentUsers.vue +4 -4
  44. package/src/components/VeCgrups.vue +3 -3
  45. package/src/components/VeCpemis.vue +3 -3
  46. package/src/components/VeCrules.vue +3 -3
  47. package/src/components/VeGrupBlngUsers.vue +6 -6
  48. package/src/components/VePemiBlngGrups.vue +6 -6
  49. package/srv.mjs +0 -4
  50. package/dist/js/app-legacy.6f82d8bd.js +0 -1
  51. package/dist/js/app.6f82d8bd.js +0 -1
@@ -0,0 +1,131 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>server/dataChannel.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">server/dataChannel.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import isestr from 'wsemi/src/isestr.mjs'
49
+
50
+
51
+ //資料通道(w-serv-webdata, useDbOrm:true)之表操作授權與 ORM 呼叫包裝(規則帳本 R-WSD-01、R-ORM-02; spec/設計要點與取捨.md ADR-026、ADR-015 Update)
52
+ //
53
+ //政策: 資料通道只開 select(methodsExec:['select'] 為第一道, 本授權函數只放行 select 為第二道).
54
+ //why: 裸 insert/save/del/delAll 繞過 procCore 之 lockSave、kmx、ckKey、寫入前驗證與整表比對(ADR-009/012/025/026 之不變式只在 kpFunExt 成立),
55
+ //同表兩條寫入路徑不對稱; 前端與測試皆只用 kpFunExt 寫入、同步只用 select(2026-10-14 Grep `\$fapi\.`、`callRpc(` 實證).
56
+ //連線者之身分授權仍在 verifyConn(getAndVerifyClientUser), 本函數不重做.
57
+
58
+
59
+ /**
60
+ * 建立資料通道之表操作授權函數(w-serv-webdata 之 opt.authorizeExec)
61
+ *
62
+ * @param {Object} p 輸入物件
63
+ * @param {Object} p.srLog 輸入 srLog(須有 error 方法)
64
+ * @returns {Function} 回傳授權函數 ({ userId, tableName, methodName }) => Boolean, 只對已取得 userId 之 select 回傳 true; 拒絕時記 srLog(event 'authorizeExec', msg 為 err key)
65
+ */
66
+ function createAuthorizeExec({ srLog }) {
67
+ return ({ userId, tableName, methodName }) => {
68
+ let msg = ''
69
+ if (!isestr(userId)) {
70
+ msg = 'cannotFindUserId'
71
+ }
72
+ else if (methodName !== 'select') {
73
+ msg = 'userNoPermission'
74
+ }
75
+ if (msg !== '') {
76
+ srLog.error({ event: 'authorizeExec', ok: false, msg, userId, tableName, methodName })
77
+ return false
78
+ }
79
+ return true
80
+ }
81
+ }
82
+
83
+
84
+ /**
85
+ * 包裝資料通道之 operOrm: ORM 拒絕之原文(英文訊息、Error)只入 srLog, 對前端改回語系鍵 anUnexpectedErrorOccurred
86
+ *
87
+ * @param {Function} procOrm 輸入 w-serv-orm 之 procOrm(userId, tableName, methodName, input)
88
+ * @param {Object} p 輸入物件
89
+ * @param {Object} p.srLog 輸入 srLog(須有 error 方法)
90
+ * @returns {Function} 回傳同簽名之 async 函數
91
+ */
92
+ function wrapOperOrm(procOrm, { srLog }) {
93
+ return async (userId, tableName, methodName, input) => {
94
+ try {
95
+ return await procOrm(userId, tableName, methodName, input)
96
+ }
97
+ catch (err) {
98
+ let raw = (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || String(err))
99
+ srLog.error({ event: 'operOrm-error', msg: 'anUnexpectedErrorOccurred', raw, userId, tableName, methodName })
100
+ return Promise.reject('anUnexpectedErrorOccurred')
101
+ }
102
+ }
103
+ }
104
+
105
+
106
+ export { createAuthorizeExec, wrapOperOrm }
107
+ </code></pre>
108
+ </article>
109
+ </section>
110
+
111
+
112
+
113
+
114
+
115
+
116
+ </div>
117
+
118
+ <br class="clear">
119
+
120
+ <footer>
121
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 16 2026 13:07:24 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
122
+ </footer>
123
+
124
+ <script>prettyPrint();</script>
125
+ <script src="scripts/polyfill.js"></script>
126
+ <script src="scripts/linenumber.js"></script>
127
+
128
+
129
+
130
+ </body>
131
+ </html>
@@ -29,7 +29,7 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -102,7 +102,7 @@ export default createLockSave
102
102
  <br class="clear">
103
103
 
104
104
  <footer>
105
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 16:35:27 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
105
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 16 2026 13:07:24 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
106
106
  </footer>
107
107
 
108
108
  <script>prettyPrint();</script>
@@ -29,7 +29,7 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -163,7 +163,7 @@ export {
163
163
  <br class="clear">
164
164
 
165
165
  <footer>
166
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 16:35:27 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
166
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 16 2026 13:07:24 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
167
167
  </footer>
168
168
 
169
169
  <script>prettyPrint();</script>
@@ -29,7 +29,7 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -57,6 +57,8 @@ import arrHas from 'wsemi/src/arrHas.mjs'
57
57
  import ltdtDiffByKey from 'wsemi/src/ltdtDiffByKey.mjs'
58
58
  import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
59
59
  import ds from '../src/schema/index.mjs'
60
+ import procLang from './procLang.mjs'
61
+ import validateRowsBeforeWrite from './validateRows.mjs'
60
62
 
61
63
 
62
64
  /**
@@ -71,7 +73,7 @@ import ds from '../src/schema/index.mjs'
71
73
  * @returns {Array} 以資料庫列為基底、僅覆蓋 pickKeys 之新列陣列(順序同資料庫)
72
74
  */
73
75
  function mergePickKeysOnly(ltdtDb, rowsIn, keyDetect, pickKeys) {
74
- let kpIn = {}
76
+ let kpIn = Object.create(null) //無原型: 鍵為使用者資料(id), 不得命中 constructor 等原型鍵(規則帳本 R-KEY-01)
75
77
  each(rowsIn, (r) => {
76
78
  let v = get(r, keyDetect, '')
77
79
  if (isestr(v)) {
@@ -144,7 +146,7 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
144
146
  let err = null
145
147
 
146
148
  //check
147
- let kp = {}
149
+ let kp = Object.create(null) //無原型: haskey 以 in 判斷, {} 之原型鍵(constructor、toString 等)恆為真而誤判重複(規則帳本 R-KEY-01)
148
150
  each(rows, (v, k) => {
149
151
 
150
152
  //value
@@ -195,6 +197,13 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
195
197
  }
196
198
  }
197
199
 
200
+ //主鍵之有限數字轉為其字串形式(規則帳本 R-ORM-04; w-serv-orm 1.1.11 起庫內即如此存放): ltdtDiffByKey 以嚴格比對, 未轉時 123 對不上庫內之 '123',
201
+ //成為「刪 '123' 再新增」而重設建立者與建立時間(userId、timeCreate); 放在 ckKey 之後, targets 仍只收字串主鍵(ckKey 以 isestr 檢查 id)
202
+ rows = map(rows, (r) => {
203
+ let id = get(r, 'id')
204
+ return (typeof id === 'number' &amp;&amp; Number.isFinite(id)) ? { ...r, id: String(id) } : r
205
+ })
206
+
198
207
  //ltdtDiffByKey
199
208
  let ltdtOld = await woItems[keyTable].select()
200
209
 
@@ -214,6 +223,23 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
214
223
  let r = ltdtDiffByKey(ltdtOld, ltdtNew, keyDetect)
215
224
  // console.log('ltdtDiffByKey r', r)
216
225
 
226
+ //寫入前驗證(規則帳本 R-ORM-01, ADR-026): 於第一個破壞步驟(del)之前擋下可預判之寫入失敗, 被拒時資料表零變更.
227
+ //why: 寫入順序為 del → insert → save, 而 w-serv-orm 1.1.8 起對「會被 clearXSS 改寫之主鍵」整批拒絕、LMDB 對 > 1978 B 之主鍵整批拒絕,
228
+ //修正前遇之即「舊列已刪、新列被拒」(改標的 id 時該列遺失, 2026-10-14 V3/V7 實測); 非主鍵欄位之標記則被靜默改寫(存值≠送入值).
229
+ //只驗本次會寫入之 add + diff 列(任一字串欄位); 未變更之 same 列不寫入、只參與主鍵重複之判定(舊資料不阻擋儲存)
230
+ let ve = validateRowsBeforeWrite([...r.add, ...r.diff], { rowsIdOnly: r.same })
231
+ if (ve !== null) {
232
+ let key = 'saveRowFieldInvalid'
233
+ if (ve.reason === 'htmlMarkup') {
234
+ key = 'htmlMarkupNotAllowed'
235
+ }
236
+ else if (ve.reason === 'idDuplicate') {
237
+ key = 'saveRowFieldDuplicate'
238
+ }
239
+ srLog.error({ event: 'updateTabItems-validate-error', keyTable, msg: key, reason: ve.reason, row: ve.k, field: ve.field })
240
+ return Promise.reject(key)
241
+ }
242
+
217
243
  //del
218
244
  if (size(r.del) > 0) {
219
245
  await procOrm(userId, keyTable, 'del', r.del) //須使用procOrm才有辦法自動給予相關欄位
@@ -244,9 +270,15 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
244
270
  }
245
271
 
246
272
 
247
- //errLog: 失敗統一記 err key (updateTabItems reject 為 key 字串如 saveRowFieldInvalid; procOrm 失敗為 Error)
273
+ //errLog: 失敗統一記 err key 並回傳對外之 key(規則帳本 R-ORM-02).
274
+ //拒絕值來源: updateTabItems 自身之 reject 為本套件語系鍵字串(saveRowFieldInvalid、htmlMarkupNotAllowed、saveInProgress 等), 原樣轉出;
275
+ //procOrm 之拒絕為 w-serv-orm 之英文原文字串(1.1.10 起, 如 failed to save data…)或 Error, updateTabItems 內直接呼叫之 woItems[keyTable].select()
276
+ //失敗為 ORM 之原錯誤(可能為 Error)——此兩類原文只入 srLog 之 raw, 對前端與外部改回 saveWriteFail(修正前原樣外送, 前端 tErr 換算為「無法連線至伺服器」)
248
277
  let errLog = (event, userId, err) => {
249
- srLog.error({ event, userId, msg: (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || event) })
278
+ let key = toOutKey(err)
279
+ let raw = (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || String(err))
280
+ srLog.error({ event, userId, msg: key, ...(raw !== key ? { raw } : {}) })
281
+ return key
250
282
  }
251
283
 
252
284
 
@@ -255,7 +287,7 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
255
287
  try {
256
288
  await updateTabItems('targets', userId, rows, 'id')
257
289
  }
258
- catch (err) { errLog('updateTargets-error', userId, err); throw err }
290
+ catch (err) { throw errLog('updateTargets-error', userId, err) }
259
291
  srLog.info({ event: 'updateTargets-success', userId })
260
292
  return 'saveTabItemsSuccess' //成功 msg key
261
293
  }
@@ -266,7 +298,7 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
266
298
  try {
267
299
  await updateTabItems('pemis', userId, rows, 'name')
268
300
  }
269
- catch (err) { errLog('updatePemis-error', userId, err); throw err }
301
+ catch (err) { throw errLog('updatePemis-error', userId, err) }
270
302
  srLog.info({ event: 'updatePemis-success', userId })
271
303
  return 'saveTabItemsSuccess' //成功 msg key
272
304
  }
@@ -277,7 +309,7 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
277
309
  try {
278
310
  await updateTabItems('grups', userId, rows, 'name')
279
311
  }
280
- catch (err) { errLog('updateGrups-error', userId, err); throw err }
312
+ catch (err) { throw errLog('updateGrups-error', userId, err) }
281
313
  srLog.info({ event: 'updateGrups-success', userId })
282
314
  return 'saveTabItemsSuccess' //成功 msg key
283
315
  }
@@ -288,7 +320,7 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
288
320
  try {
289
321
  await updateTabItems('users', userId, rows, 'id', opt)
290
322
  }
291
- catch (err) { errLog('updateUsers-error', userId, err); throw err }
323
+ catch (err) { throw errLog('updateUsers-error', userId, err) }
292
324
  srLog.info({ event: 'updateUsers-success', userId })
293
325
  return 'saveTabItemsSuccess' //成功 msg key
294
326
  }
@@ -306,7 +338,17 @@ function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
306
338
  }
307
339
 
308
340
 
309
- export { mergePickKeysOnly }
341
+ //toOutKey: 拒絕值為本套件語系鍵(procLang 之鍵集合)者原樣回傳, 其餘(ORM/I/O 原文、Error)回 keyFail(規則帳本 R-ORM-02)
342
+ let kpLangKey = null
343
+ function toOutKey(err, keyFail = 'saveWriteFail') {
344
+ if (kpLangKey === null) {
345
+ kpLangKey = new Set(Object.keys(procLang({}).eng))
346
+ }
347
+ return (typeof err === 'string' &amp;&amp; kpLangKey.has(err)) ? err : keyFail
348
+ }
349
+
350
+
351
+ export { mergePickKeysOnly, toOutKey }
310
352
 
311
353
 
312
354
  export default proc
@@ -324,7 +366,7 @@ export default proc
324
366
  <br class="clear">
325
367
 
326
368
  <footer>
327
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 16:35:27 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
369
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 16 2026 13:07:24 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
328
370
  </footer>
329
371
 
330
372
  <script>prettyPrint();</script>
@@ -0,0 +1,180 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>server/staLogs/resolveShellWorker.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">server/staLogs/resolveShellWorker.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import fs from 'fs'
49
+ import path from 'path'
50
+ import { createRequire } from 'module'
51
+
52
+
53
+ //解析 staLogs worker 檔之絕對路徑(規則帳本 R-PKG-01; X1, 對應 w-syslog 審計之方案④).
54
+ //why: 原以模組頂層之 __filename/__dirname 於載入時算出 worker 路徑——打包成 umd(toolg/gDistRollup.mjs)時,
55
+ //rollup 把 import.meta.url 換成引用 __filename 之墊片且避讓名單不含 __filename, 頂層同名宣告使 bundle 載入即 TDZ;
56
+ //改名後 __dirname 為 bundle 所在資料夾(dist/), 其下無 worker 檔. 故改為首次使用時依下列順序解析(由呼叫端快取).
57
+
58
+
59
+ let PKG_NAME = 'w-web-perm'
60
+ let REL_SHELL_WORKER = 'server/staLogs/staLogsCore.shellWorker.mjs'
61
+
62
+
63
+ //findNearestPkgDir: 由 dir 往上找最近之含 package.json 之資料夾, 找不到回 ''
64
+ function findNearestPkgDir(dir, existsSync) {
65
+ let d = dir
66
+ for (;;) {
67
+ if (existsSync(path.join(d, 'package.json'))) {
68
+ return d
69
+ }
70
+ let up = path.dirname(d)
71
+ if (up === d) {
72
+ return ''
73
+ }
74
+ d = up
75
+ }
76
+ }
77
+
78
+
79
+ /**
80
+ * 解析本套件 staLogs worker 檔之絕對路徑, 供源碼、已安裝之套件、本套件之 dist 與被下游再打包時皆可用
81
+ *
82
+ * 順序: (a) 由 fpSelf 往上找最近之 package.json, 其 name 為 pkgName 且其下 rel 存在 → 採用(最近之 package.json 名稱不符即不再往上);
83
+ * (b) createRequire(fpSelf).resolve(`${pkgName}/${rel}`)(被下游打包時往上找 node_modules); (c) 同上但以 cwd 為基準(bundle 被搬離 node_modules 樹而由專案根執行);
84
+ * (d) 皆失敗拋 Error, 訊息含套件名、相對路徑與已試之位置
85
+ *
86
+ * @param {Object} p 輸入物件
87
+ * @param {String} p.fpSelf 輸入呼叫端模組之檔案絕對路徑(由 import.meta.url 轉得; 打包後為 bundle 之位置)
88
+ * @param {String} [p.cwd=process.cwd()] 輸入 (c) 之基準資料夾
89
+ * @param {String} [p.pkgName='w-web-perm'] 輸入本套件名稱
90
+ * @param {String} [p.rel='server/staLogs/staLogsCore.shellWorker.mjs'] 輸入 worker 檔相對於套件根之路徑
91
+ * @param {Function} [p.existsSync=fs.existsSync] 輸入檔案是否存在之函數(測試注入用)
92
+ * @param {Function} [p.readFileSync=fs.readFileSync] 輸入讀檔函數(測試注入用)
93
+ * @param {Function} [p.createRequire] 輸入 module.createRequire(測試注入用)
94
+ * @returns {String} 回傳 worker 檔之絕對路徑
95
+ */
96
+ function resolveShellWorker(p = {}) {
97
+ let fpSelf = p.fpSelf
98
+ let cwd = p.cwd || process.cwd()
99
+ let pkgName = p.pkgName || PKG_NAME
100
+ let rel = p.rel || REL_SHELL_WORKER
101
+ let existsSync = p.existsSync || fs.existsSync
102
+ let readFileSync = p.readFileSync || fs.readFileSync
103
+ let createRequireFn = p.createRequire || createRequire
104
+ if (typeof fpSelf !== 'string' || fpSelf === '') {
105
+ throw new Error('invalid fpSelf')
106
+ }
107
+
108
+ let tried = []
109
+
110
+ //(a) 本模組所屬套件根
111
+ let dirPkg = findNearestPkgDir(path.dirname(fpSelf), existsSync)
112
+ if (dirPkg === '') {
113
+ tried.push(`(a) no package.json above ${path.dirname(fpSelf)}`)
114
+ }
115
+ else {
116
+ let name = ''
117
+ try {
118
+ name = JSON.parse(readFileSync(path.join(dirPkg, 'package.json'), 'utf8')).name
119
+ }
120
+ catch (err) {}
121
+ let fp = path.join(dirPkg, rel)
122
+ if (name !== pkgName) {
123
+ tried.push(`(a) ${path.join(dirPkg, 'package.json')} name[${name}]`)
124
+ }
125
+ else if (!existsSync(fp)) {
126
+ tried.push(`(a) ${fp}`)
127
+ }
128
+ else {
129
+ return fp
130
+ }
131
+ }
132
+
133
+ //(b) 由本模組位置以模組解析, (c) 由 cwd 以模組解析
134
+ let id = `${pkgName}/${rel}`
135
+ let bases = [['b', fpSelf], ['c', path.join(cwd, 'noop.js')]]
136
+ for (let [k, base] of bases) {
137
+ try {
138
+ let fp = createRequireFn(base).resolve(id)
139
+ if (existsSync(fp)) {
140
+ return fp
141
+ }
142
+ tried.push(`(${k}) ${fp}`)
143
+ }
144
+ catch (err) {
145
+ tried.push(`(${k}) resolve ${id} from ${base}`)
146
+ }
147
+ }
148
+
149
+ //(d)
150
+ throw new Error(`cannot resolve ${rel} of ${pkgName} (the package must be installed with its server folder), tried: ${tried.join('; ')}`)
151
+ }
152
+
153
+
154
+ export { PKG_NAME, REL_SHELL_WORKER }
155
+ export default resolveShellWorker
156
+ </code></pre>
157
+ </article>
158
+ </section>
159
+
160
+
161
+
162
+
163
+
164
+
165
+ </div>
166
+
167
+ <br class="clear">
168
+
169
+ <footer>
170
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 16 2026 13:07:24 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
171
+ </footer>
172
+
173
+ <script>prettyPrint();</script>
174
+ <script src="scripts/polyfill.js"></script>
175
+ <script src="scripts/linenumber.js"></script>
176
+
177
+
178
+
179
+ </body>
180
+ </html>