w-web-perm 1.1.11 → 1.1.13

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 (122) hide show
  1. package/README.md +158 -117
  2. package/dist/css/app.f09c7433.css +1 -0
  3. package/dist/index.tmp +1 -1
  4. package/dist/js/app-legacy.8af87052.js +1 -0
  5. package/dist/js/app.8af87052.js +1 -0
  6. package/dist/js/{chunk-vendors.3e9e3ef1.js → chunk-vendors-legacy.ece5a361.js} +1 -1
  7. package/dist/js/{chunk-vendors-legacy.3e9e3ef1.js → chunk-vendors.ece5a361.js} +1 -1
  8. package/dist/w-web-perm.umd.js +2 -2
  9. package/dist/w-web-perm.umd.js.map +1 -1
  10. package/docs/WWebPerm.html +84 -4
  11. package/docs/global.html +24747 -832
  12. package/docs/index.html +2 -2
  13. package/docs/server_WWebPerm.mjs.html +245 -32
  14. package/docs/server_cascadeWrite.mjs.html +225 -0
  15. package/docs/server_dataChannel.mjs.html +2 -2
  16. package/docs/server_keyCheck.mjs.html +353 -0
  17. package/docs/server_lockSave.mjs.html +2 -2
  18. package/docs/server_maskLog.mjs.html +2 -2
  19. package/docs/server_procCore.mjs.html +180 -41
  20. package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
  21. package/docs/server_syncKeep.mjs.html +311 -0
  22. package/docs/server_tools_authRefsAudit.mjs.html +247 -0
  23. package/docs/server_tools_authRefsClean.mjs.html +335 -0
  24. package/docs/server_tools_fromTools.mjs.html +797 -0
  25. package/docs/server_tools_isMain.mjs.html +103 -0
  26. package/docs/server_tools_nameDupReport.mjs.html +280 -0
  27. package/docs/server_validateRows.mjs.html +15 -4
  28. package/docs/server_wrapInjected.mjs.html +3 -3
  29. package/docs/src_fetchJson.mjs.html +2 -2
  30. package/docs/src_js_cvKpToArr.mjs.html +125 -0
  31. package/docs/src_js_cvKpToObj.mjs.html +210 -0
  32. package/docs/src_js_draftSync.mjs.html +142 -0
  33. package/docs/src_js_eachOwn.mjs.html +3 -3
  34. package/docs/src_js_editorRefRows.mjs.html +356 -0
  35. package/docs/src_js_externalRows.mjs.html +338 -0
  36. package/docs/src_js_externalUi.mjs.html +230 -0
  37. package/docs/src_js_findRefRows.mjs.html +107 -0
  38. package/docs/src_js_findTouchedDup.mjs.html +150 -0
  39. package/docs/src_js_fmtLossyMsg.mjs.html +95 -0
  40. package/docs/src_js_fromAudit.mjs.html +489 -0
  41. package/docs/src_js_getAddImpact.mjs.html +269 -0
  42. package/docs/src_js_getAuthWarn.mjs.html +463 -0
  43. package/docs/src_js_getCascadePreview.mjs.html +258 -0
  44. package/docs/src_js_getDelRefCount.mjs.html +210 -0
  45. package/docs/src_js_getEffMode.mjs.html +92 -0
  46. package/docs/src_js_getKpLossy.mjs.html +155 -0
  47. package/docs/src_js_getNameErrs.mjs.html +266 -0
  48. package/docs/src_js_getOwn.mjs.html +3 -3
  49. package/docs/src_js_getPropKey.mjs.html +109 -0
  50. package/docs/src_js_getRuleEnable.mjs.html +2 -2
  51. package/docs/src_js_isAuthMode.mjs.html +92 -0
  52. package/docs/src_js_joinNames.mjs.html +96 -0
  53. package/docs/src_js_legendFit.mjs.html +2 -2
  54. package/docs/src_js_pairRowsToSnapshot.mjs.html +156 -0
  55. package/docs/src_js_refAudit.mjs.html +359 -0
  56. package/docs/src_js_refCascade.mjs.html +572 -0
  57. package/docs/src_js_relDialogSend.mjs.html +199 -0
  58. package/docs/src_js_rowDigest.mjs.html +265 -0
  59. package/docs/src_plugins_dataReloadGuard.mjs.html +109 -0
  60. package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
  61. package/docs/src_plugins_submitGuard.mjs.html +2 -2
  62. package/g_authRefsClean.mjs +79 -0
  63. package/g_fixFromType.mjs +92 -0
  64. package/g_initialData.mjs +3 -3
  65. package/g_purgeFrom.mjs +127 -0
  66. package/package.json +1 -1
  67. package/server/WWebPerm.mjs +243 -30
  68. package/server/cascadeWrite.mjs +153 -0
  69. package/server/keyCheck.mjs +281 -0
  70. package/server/procCore.mjs +178 -39
  71. package/server/procLang.mjs +271 -0
  72. package/server/syncKeep.mjs +239 -0
  73. package/server/tools/authRefsAudit.mjs +175 -0
  74. package/server/tools/authRefsClean.mjs +263 -0
  75. package/server/tools/fromTools.mjs +725 -0
  76. package/server/tools/isMain.mjs +31 -0
  77. package/server/tools/nameDupReport.mjs +208 -0
  78. package/server/validateRows.mjs +13 -2
  79. package/server/wrapInjected.mjs +1 -1
  80. package/src/components/LayoutContentGrups.vue +399 -77
  81. package/src/components/LayoutContentPemis.vue +400 -78
  82. package/src/components/LayoutContentTargets.vue +261 -25
  83. package/src/components/LayoutContentUsers.vue +336 -98
  84. package/src/components/VeCgrups.vue +119 -115
  85. package/src/components/VeCpemis.vue +117 -115
  86. package/src/components/VeCrules.vue +18 -2
  87. package/src/components/VeGrupBlngUsers.vue +240 -73
  88. package/src/components/VePemiBlngGrups.vue +247 -83
  89. package/src/js/cvKpToArr.mjs +53 -0
  90. package/src/js/cvKpToObj.mjs +138 -0
  91. package/src/js/draftSync.mjs +70 -0
  92. package/src/js/eachOwn.mjs +1 -1
  93. package/src/js/editorRefRows.mjs +284 -0
  94. package/src/js/externalRows.mjs +266 -0
  95. package/src/js/externalUi.mjs +158 -0
  96. package/src/js/findRefRows.mjs +35 -0
  97. package/src/js/findTouchedDup.mjs +78 -0
  98. package/src/js/fmtLossyMsg.mjs +23 -0
  99. package/src/js/fromAudit.mjs +417 -0
  100. package/src/js/getAddImpact.mjs +197 -0
  101. package/src/js/getAuthWarn.mjs +391 -0
  102. package/src/js/getCascadePreview.mjs +186 -0
  103. package/src/js/getDelRefCount.mjs +138 -0
  104. package/src/js/getEffMode.mjs +20 -0
  105. package/src/js/getKpLossy.mjs +83 -0
  106. package/src/js/getNameErrs.mjs +194 -0
  107. package/src/js/getOwn.mjs +1 -1
  108. package/src/js/getPropKey.mjs +37 -0
  109. package/src/js/isAuthMode.mjs +20 -0
  110. package/src/js/joinNames.mjs +24 -0
  111. package/src/js/pairRowsToSnapshot.mjs +84 -0
  112. package/src/js/refAudit.mjs +287 -0
  113. package/src/js/refCascade.mjs +500 -0
  114. package/src/js/relDialogSend.mjs +127 -0
  115. package/src/js/rowDigest.mjs +193 -0
  116. package/src/plugins/dataReloadGuard.mjs +37 -0
  117. package/src/plugins/mShare.mjs +147 -174
  118. package/src/plugins/mUI.mjs +3 -8
  119. package/src/schema/tables/users.mjs +152 -152
  120. package/dist/css/app.6918ec45.css +0 -1
  121. package/dist/js/app-legacy.963e800c.js +0 -1
  122. package/dist/js/app.963e800c.js +0 -1
@@ -0,0 +1,156 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>src/js/pairRowsToSnapshot.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#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><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#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</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#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</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#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">src/js/pairRowsToSnapshot.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>//反向關聯對話框(VeGrupBlngUsers/VePemiBlngGrups)之「列 ↔ 資料快照」身分配對
49
+ //規則見 sgud/rule/rulebook.md「關聯對話框容錯、以身分配對、資料驅動屬性鍵」之 R-REL-PAIR-01
50
+ //本檔無外部相依
51
+
52
+
53
+ //toId: 身分值正規化——非空字串,或有限數字(ADR-028: 數字主鍵以其字串形式處置);其餘(空字串、NaN、物件、null、undefined)視為無身分
54
+ function toId(v) {
55
+ if (typeof v === 'string' &amp;&amp; v !== '') {
56
+ return v
57
+ }
58
+ if (typeof v === 'number' &amp;&amp; Number.isFinite(v)) {
59
+ return String(v)
60
+ }
61
+ return null
62
+ }
63
+
64
+
65
+ /**
66
+ * 把對話框之列以「列身分」配對到存檔當下取得之資料快照(不用列索引)
67
+ *
68
+ * 反向關聯對話框(群組頁「所屬使用者」、權限頁「所屬群組」)開啟時依 store 當時的列序產生對話框的列; 存檔時重新取 store 快照.
69
+ * 兩者以列索引配對時, store 於開框期間被更新(另一位管理者增刪列或改 order)即錯位, 關聯寫到另一個使用者/群組(規則帳本 R-REL-PAIR-01).
70
+ * 本函數以身分欄位(預設 id)配對: 每列配到快照中身分相同(且 verifyKeys 欄位值全相等)的列; 配不到者列入 missing,
71
+ * 由呼叫端整次拒存並提示重新整理, 不得退而配到其他列.
72
+ *
73
+ * @param {Array} rows 輸入對話框之列, 每列帶身分欄位(opt.idKey); 非陣列時回傳空結果
74
+ * @param {Array} snapshot 輸入存檔當下取得之資料表快照(列物件陣列); 缺身分欄位之快照列不參與配對, 身分重複者取第一筆
75
+ * @param {Object} [opt={}] 輸入設定物件
76
+ * @param {String} [opt.idKey='id'] 輸入身分欄位名稱, 預設'id'
77
+ * @param {Array} [opt.verifyKeys=[]] 輸入除身分外另須與快照相等之欄位名稱陣列(如群組之 'name': 改名視為過期), 預設[]
78
+ * @returns {Object} 回傳 { pairs, missing, krs, matched }; pairs 與 rows 等長, pairs[kr] 為配到之快照列(同一個物件參考),配不到為 null;
79
+ * missing 為配不到之列(原物件)陣列; krs 為配得到之列的列索引(依列序), matched[i] 為 krs[i] 那一列配到之快照列(兩者等長、同序, 供「只處理配得到者」之顯示重算用, 免呼叫端自行壓縮索引)
80
+ * @example
81
+ *
82
+ * let snapshot = [{ id: 'u2', name: 'b' }, { id: 'u1', name: 'a' }]
83
+ * let rows = [{ id: 'u1', name: 'a' }, { id: 'u3', name: 'c' }]
84
+ * let { pairs, missing, krs } = pairRowsToSnapshot(rows, snapshot)
85
+ * console.log(pairs[0].name, pairs[1], missing.length, krs)
86
+ * // => 'a' null 1 [0]
87
+ *
88
+ */
89
+ function pairRowsToSnapshot(rows, snapshot, opt = {}) {
90
+ let pairs = []
91
+ let missing = []
92
+ let krs = []
93
+ let matched = []
94
+ if (!Array.isArray(rows)) {
95
+ return { pairs, missing, krs, matched }
96
+ }
97
+ let idKey = (opt !== null &amp;&amp; typeof opt === 'object' &amp;&amp; typeof opt.idKey === 'string' &amp;&amp; opt.idKey !== '') ? opt.idKey : 'id'
98
+ let verifyKeys = (opt !== null &amp;&amp; typeof opt === 'object' &amp;&amp; Array.isArray(opt.verifyKeys)) ? opt.verifyKeys : []
99
+
100
+ //kp: 身分 → 快照列(Map: 身分為 __proto__/constructor 等字串時亦精確)
101
+ let kp = new Map()
102
+ if (Array.isArray(snapshot)) {
103
+ for (let it of snapshot) {
104
+ let id = (it !== null &amp;&amp; typeof it === 'object') ? toId(it[idKey]) : null
105
+ if (id !== null &amp;&amp; !kp.has(id)) {
106
+ kp.set(id, it)
107
+ }
108
+ }
109
+ }
110
+
111
+ for (let kr = 0; kr &lt; rows.length; kr++) { //索引迴圈(不用 forEach: 稀疏陣列之空洞須視為無身分列入 missing, pairs 與 rows 等長)
112
+ let r = rows[kr]
113
+ let id = (r !== null &amp;&amp; typeof r === 'object') ? toId(r[idKey]) : null
114
+ let it = (id === null) ? undefined : kp.get(id)
115
+ if (it !== undefined &amp;&amp; verifyKeys.every((k) => r[k] === it[k])) {
116
+ pairs.push(it)
117
+ krs.push(kr)
118
+ matched.push(it)
119
+ }
120
+ else {
121
+ pairs.push(null)
122
+ missing.push(r)
123
+ }
124
+ }
125
+
126
+ return { pairs, missing, krs, matched }
127
+ }
128
+
129
+
130
+ export { toId }
131
+ export default pairRowsToSnapshot
132
+ </code></pre>
133
+ </article>
134
+ </section>
135
+
136
+
137
+
138
+
139
+
140
+
141
+ </div>
142
+
143
+ <br class="clear">
144
+
145
+ <footer>
146
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 10:56:39 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
147
+ </footer>
148
+
149
+ <script>prettyPrint();</script>
150
+ <script src="scripts/polyfill.js"></script>
151
+ <script src="scripts/linenumber.js"></script>
152
+
153
+
154
+
155
+ </body>
156
+ </html>
@@ -0,0 +1,359 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>src/js/refAudit.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#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><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#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</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#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</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#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">src/js/refAudit.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import JSON5 from 'json5'
49
+ import cvKpToArr from './cvKpToArr.mjs'
50
+ import findRefRows from './findRefRows.mjs'
51
+ import getEffMode from './getEffMode.mjs'
52
+ import getPropKey from './getPropKey.mjs'
53
+ import isAuthMode from './isAuthMode.mjs'
54
+ import { REF_OF, rewriteRefs } from './refCascade.mjs'
55
+
56
+
57
+ //懸空引用稽核(升版一次性清理工具之核心; 規則帳本 R-REF-CASCADE-01 之配套, 適用表 n 上線前之舊資料可能已有懸空引用):
58
+ // users.cgrups 引用不存在之群組名、grups.cpemis 引用不存在之權限名、pemis.crules 引用不存在之管控對象 id.
59
+ // 清理=把懸空項目自授權字串移除(不論 isActive), 效果等同舊版「找不到即忽略」; 改寫一律經 refCascade.rewriteRefs(物件形式仍物件、陣列形式仍陣列、未命中者同一字串), 不另寫一套.
60
+ //本檔為純函數(不依賴 Vue/DOM/ORM), 供離線稽核(server/tools/authRefsAudit.mjs)、一次性清理(server/tools/authRefsClean.mjs/g_authRefsClean.mjs)與日後頁面共用.
61
+
62
+
63
+ //EFFECT: 懸空項目對授權之影響說明(item.effect 之碼 → 文字; 依 src/plugins/mShare.mjs 之 mergeRules, R-AUTH-MERGE-00/R-PRINCIPLE-00 c)
64
+ const EFFECT = {
65
+ inactive: '停用項不參與授權合併, 移除不改變授權',
66
+ and: '啟用且有效模式為 AND(含 mode 缺/非法): 引用查無實體者內容全 n, 使該層合併結果為空(收嚴); 清理後此限制解除, 授權可能變多',
67
+ or: '啟用且為 OR: 引用查無實體者聯集貢獻 0; 惟若為同層唯一之 OR 項而同層另有合法 AND 項, 現況基底為空集(結果為空), 清理後基底改取 AND 項, 授權可能變多',
68
+ rule: 'crules 查無之管控對象 id: 授權輸出只列現存管控對象, 不影響授權',
69
+ }
70
+
71
+
72
+ let isStr = (v) => typeof v === 'string'
73
+ let arr = (v) => (Array.isArray(v) ? v : [])
74
+
75
+
76
+ //mkIsDang: 建立「引用名稱查無實體」之判定函數(key → 是否懸空); grups/pemis 經 findRefRows(單一擁有者, 缺 name 視為 ''、===、歷史同名不是懸空, 同伺服端 getUserRules; 同一名稱只查一次,
77
+ //免使用者數 × 引用數 × 群組數之逐筆比對), targets 以 id 轉屬性鍵比對(同 mergeRules/getUserRules 之取值, 只收字串與數字之 id)
78
+ let mkIsDang = (rows, keyTable) => {
79
+ if (keyTable === 'targets') {
80
+ let s = new Set()
81
+ arr(rows).forEach((r) => {
82
+ if (r !== null &amp;&amp; typeof r === 'object' &amp;&amp; (isStr(r.id) || typeof r.id === 'number')) {
83
+ let k = getPropKey(r.id)
84
+ if (k !== null) {
85
+ s.add(k)
86
+ }
87
+ }
88
+ })
89
+ return (k) => !s.has(k)
90
+ }
91
+ let memo = new Map()
92
+ return (k) => {
93
+ if (!memo.has(k)) {
94
+ memo.set(k, findRefRows(rows, k).length === 0)
95
+ }
96
+ return memo.get(k)
97
+ }
98
+ }
99
+
100
+
101
+ //modeInfo: mode 之合法性(isAuthMode)與有效模式是否為 OR(getEffMode; mode 缺或非法者有效模式為 AND, 只作限制項, 見 mergeRules)
102
+ let modeInfo = (m) => ({ valid: isAuthMode(m), isOr: getEffMode(m) === 'OR' })
103
+
104
+
105
+ //decode: 授權字串 → { state, ents }; 'empty'(非字串或空白, 新增列之預設值, 非損壞)、'bad'(JSON5 解析失敗, 或解出非物件/陣列, 同 getAuthWarn.isBadData; reason 為 parse/notContainer/decode)、'ok'(ents 為 cvKpToArr 之解碼)
106
+ let decode = (str) => {
107
+ if (!isStr(str) || str.trim() === '') {
108
+ return { state: 'empty', ents: [] }
109
+ }
110
+ let kp = null
111
+ try {
112
+ kp = JSON5.parse(str)
113
+ }
114
+ catch (err) {
115
+ return { state: 'bad', reason: 'parse', ents: [] }
116
+ }
117
+ if (kp === null || typeof kp !== 'object') {
118
+ return { state: 'bad', reason: 'notContainer', ents: [] }
119
+ }
120
+ try {
121
+ return { state: 'ok', reason: '', ents: cvKpToArr(kp) }
122
+ }
123
+ catch (err) {
124
+ return { state: 'bad', reason: 'decode', ents: [] }
125
+ }
126
+ }
127
+
128
+
129
+ //maybeNonEmpty: 同層啟用項(es: [{ valid, isOr, zero }], 見 modeInfo)合併結果「可能非空」否, 依 mergeRules 之結構, 內容未知之實體項視為可能非空, zero 者(查無實體之懸空項, 或名稱非字串而不對應任何實體者)內容恆空:
130
+ // 沒有任何 mode 合法(OR/AND)之項 → 恆空; 任一 AND 有效項(含 mode 缺/非法, 只作限制)為 zero → 恆空(交集含空集); 有 OR 項時須至少一個 OR 項非 zero(聯集), 無 OR 項時以合法 AND 項為基底即可
131
+ let maybeNonEmpty = (es) => {
132
+ if (!es.some((e) => e.valid)) {
133
+ return false
134
+ }
135
+ if (es.some((e) => !e.isOr &amp;&amp; e.zero)) {
136
+ return false
137
+ }
138
+ if (es.some((e) => e.isOr)) {
139
+ return es.some((e) => e.isOr &amp;&amp; !e.zero)
140
+ }
141
+ return true
142
+ }
143
+
144
+
145
+ /**
146
+ * 稽核引用端授權字串中查無實體(懸空)之引用, 並產生清理後之新字串(純函數, 不讀寫任何資料)
147
+ *
148
+ * 懸空判準同伺服端 findRefRows: cgrups/cpemis 以名稱 === 比對 grups/pemis 之 name(缺 name 視為 ''), 歷史同名(名稱對到多列)不是懸空;
149
+ * crules 之項目名稱以 getPropKey 轉屬性鍵後與 targets.id 比對. 解碼用 cvKpToArr(與伺服端 getUserRules 同一份); JSON5 解析失敗(或非物件/陣列)者列入 badJson, 不當作有引用、不動.
150
+ * 名稱非字串(陣列元素無 name、name 為數字或陣列等)之 cgrups/cpemis 項目不對應任何群組/權限名, 無法以名稱處置, 列入 unnamed(不清理, 須人工於後台處理; 編輯器對其亦拒存).
151
+ * 清理移除懸空項目不論 isActive; 不回顯 email/name 等個資(只列主鍵 id 與被引用之名稱).
152
+ *
153
+ * 清理後授權之變化(對照 mergeRules): 停用項、crules 項不影響授權; 啟用之 AND 項(含 mode 缺/非法)使該層結果為空, 清理後限制解除;
154
+ * 啟用之 OR 項聯集貢獻 0, 但若為同層唯一之 OR 項且同層另有合法 AND 項, 現況基底為空集, 清理後基底改取 AND 項. 以上皆只會放寬(授權變多), 不會收嚴.
155
+ * widen 以結構判定「可能變多」之引用端(實際是否變多取決於各實體內容, 為保守上界): users 含直接(自身 cgrups)與經群組傳導(啟用引用之群組其 cpemis 可能變多).
156
+ *
157
+ * @param {Object} d 輸入 { users, grups, pemis, targets }(各為列陣列, 缺者視為空)
158
+ * @returns {Object} 回傳 { items, counts, changes, badJson, unnamed, unsafe, noId, widen }:
159
+ * items 懸空項目 [{ table(users|grups|pemis), holderId, field, name, isActive, mode, effect }], effect 為 EFFECT 之碼(inactive|and|or|rule), mode 於 crules 為 null
160
+ * counts { nItems, nActive, nInactive, nAndActive, nOrActive, nBadJson, nUnnamed, nNoId, nChanges, nAndActiveHolders, nWidenUsers, nWidenGrups,
161
+ * byTable: { users|grups|pemis: { nItems, nHolders, nAndActive, nAndActiveHolders, nWidenHolders } } }; nAndActive 為啟用且有效模式為 AND 之懸空項數(ref 類), nAndActiveHolders 為擁有該類項目之使用者與群組列數
162
+ * changes [{ table, id, field, before, after, nDel }], after 為清理後新字串(經 rewriteRefs)
163
+ * badJson [{ table, id, field, reason }]; unnamed 為 [{ table, holderId, field, isActive, mode }]; unsafe 為含無法安全重新編碼之內容(如 JSON5 Infinity/NaN 數值, 重新編碼會變成 null 而改指別的規則名)故不清理之 [{ table, id, field }](須人工處理); noId 為缺字串主鍵而無從寫回之 [{ table, holderId, field, nItems }]
164
+ * widen [{ table(users|grups), holderId, field, via(direct|grups) }]
165
+ * @example
166
+ *
167
+ * let r = auditRefs({
168
+ * users: [{ id: 'u1', cgrups: '{"G":{"mode":"AND","isActive":"y"},"X":{"mode":"AND","isActive":"y"}}' }],
169
+ * grups: [{ id: 'g1', name: 'G', cpemis: '{}' }],
170
+ * pemis: [],
171
+ * targets: [],
172
+ * })
173
+ * console.log(r.items[0].name, r.changes[0].after)
174
+ * // => X {"G":{"mode":"AND","isActive":"y"}}
175
+ *
176
+ */
177
+ function auditRefs(d) {
178
+ let data = (d !== null &amp;&amp; typeof d === 'object') ? d : {}
179
+ let items = []
180
+ let changes = []
181
+ let badJson = []
182
+ let unnamed = []
183
+ let unsafe = [] //含無法安全重新編碼之內容(如 JSON5 之 Infinity/NaN 數值)而不改寫之容器
184
+ let noId = []
185
+ let widen = []
186
+ let byTable = {}
187
+
188
+ //userRefs: 各使用者啟用引用之群組名(供經群組傳導之 widen); flagGrupNames: 有 widen 之群組列之名稱
189
+ let userRefs = []
190
+ let flagGrupNames = new Set()
191
+ let flagUsers = new Set()
192
+
193
+ for (let key of Object.keys(REF_OF)) {
194
+ let sp = REF_OF[key]
195
+ let t = sp.holder
196
+ let isRule = sp.kind === 'rule'
197
+ let isDangName = mkIsDang(data[key], key)
198
+ let tab = { nItems: 0, nHolders: 0, nAndActive: 0, nAndActiveHolders: 0, nWidenHolders: 0 }
199
+ byTable[t] = tab
200
+
201
+ for (let h of arr(data[t])) {
202
+ if (h === null || typeof h !== 'object') {
203
+ continue
204
+ }
205
+ let str = h[sp.field]
206
+ let dc = decode(str)
207
+ if (dc.state === 'bad') {
208
+ badJson.push({ table: t, id: h.id, field: sp.field, reason: dc.reason })
209
+ continue
210
+ }
211
+
212
+ //逐項: 懸空(dang)、名稱非字串(unnamed, 仍參與合併而內容恆空)、其餘(實體存在)
213
+ let dang = []
214
+ let es = []
215
+ let activeNames = []
216
+ for (let e of dc.ents) {
217
+ let active = e.isActive === 'y'
218
+ let k = null
219
+ if (isRule) {
220
+ k = getPropKey(e.name)
221
+ if (k === null) {
222
+ continue //屬性鍵轉型必拋者, 伺服端 mergeRules 略過, 不對應任何標的
223
+ }
224
+ }
225
+ else {
226
+ if (!isStr(e.name)) {
227
+ unnamed.push({ table: t, holderId: h.id, field: sp.field, isActive: e.isActive, mode: e.mode })
228
+ es.push({ ...modeInfo(e.mode), active, zero: true, dang: false })
229
+ continue
230
+ }
231
+ k = e.name
232
+ }
233
+ let isDang = isDangName(k)
234
+ if (!isRule) {
235
+ es.push({ ...modeInfo(e.mode), active, zero: isDang, dang: isDang })
236
+ if (active) {
237
+ activeNames.push(k)
238
+ }
239
+ }
240
+ if (isDang) {
241
+ dang.push({ key: k, e, active })
242
+ }
243
+ }
244
+ if (t === 'users' &amp;&amp; isStr(h.id)) {
245
+ userRefs.push({ id: h.id, names: activeNames })
246
+ }
247
+ if (dang.length === 0) {
248
+ continue
249
+ }
250
+ if (!isStr(h.id)) {
251
+ noId.push({ table: t, holderId: h.id, field: sp.field, nItems: dang.length })
252
+ continue
253
+ }
254
+
255
+ //items
256
+ let nAnd = 0
257
+ for (let x of dang) {
258
+ let effect = 'rule'
259
+ if (!isRule) {
260
+ effect = !x.active ? 'inactive' : (modeInfo(x.e.mode).isOr ? 'or' : 'and')
261
+ }
262
+ if (effect === 'and') {
263
+ nAnd++
264
+ }
265
+ items.push({ table: t, holderId: h.id, field: sp.field, name: x.key, isActive: x.e.isActive, mode: isRule ? null : x.e.mode, effect })
266
+ }
267
+ tab.nItems += dang.length
268
+ tab.nHolders++
269
+ tab.nAndActive += nAnd
270
+ if (nAnd > 0) {
271
+ tab.nAndActiveHolders++
272
+ }
273
+
274
+ //changes: 以 refCascade.rewriteRefs 移除該容器內之懸空名稱(不論 isActive; 歷史同名非懸空不在集合內)
275
+ let r = rewriteRefs(str, { kind: sp.kind, del: new Set(dang.map((x) => x.key)) })
276
+ if (r.unsafe) {
277
+ unsafe.push({ table: t, id: h.id, field: sp.field })
278
+ }
279
+ else if (r.changed) {
280
+ changes.push({ table: t, id: h.id, field: sp.field, before: str, after: r.str, nDel: r.nDel })
281
+ }
282
+
283
+ //widen: 同層啟用項, 現況合併結果必為空而清理後可能非空
284
+ if (!isRule) {
285
+ let A = es.filter((x) => x.active)
286
+ let R = A.filter((x) => !x.dang)
287
+ if (A.some((x) => x.dang) &amp;&amp; !maybeNonEmpty(A) &amp;&amp; maybeNonEmpty(R)) {
288
+ widen.push({ table: t, holderId: h.id, field: sp.field, via: 'direct' })
289
+ tab.nWidenHolders++
290
+ if (t === 'grups') {
291
+ flagGrupNames.add(h.name === undefined ? '' : h.name)
292
+ }
293
+ if (t === 'users') {
294
+ flagUsers.add(h.id)
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ //經群組傳導: 使用者啟用引用某群組名, 該名稱之群組列有 widen(其 cpemis 清理後授權可能變多)
302
+ if (flagGrupNames.size > 0) {
303
+ for (let u of userRefs) {
304
+ if (!flagUsers.has(u.id) &amp;&amp; u.names.some((n) => flagGrupNames.has(n))) {
305
+ widen.push({ table: 'users', holderId: u.id, field: 'cgrups', via: 'grups' })
306
+ byTable.users.nWidenHolders++
307
+ }
308
+ }
309
+ }
310
+
311
+ let refItems = items.filter((x) => x.effect !== 'rule')
312
+ let counts = {
313
+ nItems: items.length,
314
+ nActive: refItems.filter((x) => x.effect !== 'inactive').length,
315
+ nInactive: refItems.filter((x) => x.effect === 'inactive').length,
316
+ nAndActive: refItems.filter((x) => x.effect === 'and').length,
317
+ nOrActive: refItems.filter((x) => x.effect === 'or').length,
318
+ nBadJson: badJson.length,
319
+ nUnnamed: unnamed.length,
320
+ nUnsafe: unsafe.length,
321
+ nNoId: noId.length,
322
+ nChanges: changes.length,
323
+ nAndActiveHolders: byTable.users.nAndActiveHolders + byTable.grups.nAndActiveHolders,
324
+ nWidenUsers: byTable.users.nWidenHolders,
325
+ nWidenGrups: byTable.grups.nWidenHolders,
326
+ byTable,
327
+ }
328
+
329
+ return { items, counts, changes, badJson, unnamed, unsafe, noId, widen }
330
+ }
331
+
332
+
333
+ export { EFFECT, auditRefs }
334
+ export default auditRefs
335
+ </code></pre>
336
+ </article>
337
+ </section>
338
+
339
+
340
+
341
+
342
+
343
+
344
+ </div>
345
+
346
+ <br class="clear">
347
+
348
+ <footer>
349
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 10:56:39 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
350
+ </footer>
351
+
352
+ <script>prettyPrint();</script>
353
+ <script src="scripts/polyfill.js"></script>
354
+ <script src="scripts/linenumber.js"></script>
355
+
356
+
357
+
358
+ </body>
359
+ </html>