w-web-perm 1.1.12 → 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.
- package/README.md +158 -123
- package/dist/css/app.f09c7433.css +1 -0
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.8af87052.js +1 -0
- package/dist/js/app.8af87052.js +1 -0
- package/dist/js/{chunk-vendors.3e9e3ef1.js → chunk-vendors-legacy.ece5a361.js} +1 -1
- package/dist/js/{chunk-vendors-legacy.3e9e3ef1.js → chunk-vendors.ece5a361.js} +1 -1
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +84 -4
- package/docs/global.html +23023 -1125
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +232 -32
- package/docs/server_cascadeWrite.mjs.html +225 -0
- package/docs/server_dataChannel.mjs.html +2 -2
- package/docs/server_keyCheck.mjs.html +353 -0
- package/docs/server_lockSave.mjs.html +2 -2
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +180 -41
- package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
- package/docs/server_syncKeep.mjs.html +311 -0
- package/docs/server_tools_authRefsAudit.mjs.html +247 -0
- package/docs/server_tools_authRefsClean.mjs.html +335 -0
- package/docs/server_tools_fromTools.mjs.html +797 -0
- package/docs/server_tools_isMain.mjs.html +103 -0
- package/docs/server_tools_nameDupReport.mjs.html +280 -0
- package/docs/server_validateRows.mjs.html +2 -2
- package/docs/server_wrapInjected.mjs.html +3 -3
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_js_cvKpToArr.mjs.html +2 -2
- package/docs/src_js_cvKpToObj.mjs.html +44 -33
- package/docs/src_js_draftSync.mjs.html +142 -0
- package/docs/src_js_eachOwn.mjs.html +3 -3
- package/docs/src_js_editorRefRows.mjs.html +356 -0
- package/docs/src_js_externalRows.mjs.html +338 -0
- package/docs/src_js_externalUi.mjs.html +230 -0
- package/docs/src_js_findRefRows.mjs.html +107 -0
- package/docs/src_js_findTouchedDup.mjs.html +150 -0
- package/docs/src_js_fmtLossyMsg.mjs.html +2 -2
- package/docs/src_js_fromAudit.mjs.html +489 -0
- package/docs/src_js_getAddImpact.mjs.html +269 -0
- package/docs/src_js_getAuthWarn.mjs.html +463 -0
- package/docs/src_js_getCascadePreview.mjs.html +258 -0
- package/docs/src_js_getDelRefCount.mjs.html +210 -0
- package/docs/src_js_getEffMode.mjs.html +92 -0
- package/docs/src_js_getKpLossy.mjs.html +47 -52
- package/docs/src_js_getNameErrs.mjs.html +266 -0
- package/docs/src_js_getOwn.mjs.html +3 -3
- package/docs/src_js_getPropKey.mjs.html +109 -0
- package/docs/src_js_getRuleEnable.mjs.html +2 -2
- package/docs/src_js_isAuthMode.mjs.html +4 -4
- package/docs/src_js_joinNames.mjs.html +2 -2
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_js_pairRowsToSnapshot.mjs.html +156 -0
- package/docs/src_js_refAudit.mjs.html +359 -0
- package/docs/src_js_refCascade.mjs.html +572 -0
- package/docs/src_js_relDialogSend.mjs.html +199 -0
- package/docs/src_js_rowDigest.mjs.html +265 -0
- package/docs/src_plugins_dataReloadGuard.mjs.html +109 -0
- package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
- package/docs/src_plugins_submitGuard.mjs.html +2 -2
- package/g_authRefsClean.mjs +79 -0
- package/g_fixFromType.mjs +92 -0
- package/g_initialData.mjs +3 -3
- package/g_purgeFrom.mjs +127 -0
- package/package.json +1 -1
- package/server/WWebPerm.mjs +230 -30
- package/server/cascadeWrite.mjs +153 -0
- package/server/keyCheck.mjs +281 -0
- package/server/procCore.mjs +178 -39
- package/server/procLang.mjs +270 -3
- package/server/syncKeep.mjs +239 -0
- package/server/tools/authRefsAudit.mjs +175 -0
- package/server/tools/authRefsClean.mjs +263 -0
- package/server/tools/fromTools.mjs +725 -0
- package/server/tools/isMain.mjs +31 -0
- package/server/tools/nameDupReport.mjs +208 -0
- package/server/wrapInjected.mjs +1 -1
- package/src/components/LayoutContentGrups.vue +396 -75
- package/src/components/LayoutContentPemis.vue +395 -75
- package/src/components/LayoutContentTargets.vue +261 -25
- package/src/components/LayoutContentUsers.vue +334 -97
- package/src/components/VeCgrups.vue +88 -129
- package/src/components/VeCpemis.vue +86 -129
- package/src/components/VeCrules.vue +15 -5
- package/src/components/VeGrupBlngUsers.vue +202 -89
- package/src/components/VePemiBlngGrups.vue +209 -99
- package/src/js/cvKpToObj.mjs +42 -31
- package/src/js/draftSync.mjs +70 -0
- package/src/js/eachOwn.mjs +1 -1
- package/src/js/editorRefRows.mjs +284 -0
- package/src/js/externalRows.mjs +266 -0
- package/src/js/externalUi.mjs +158 -0
- package/src/js/findRefRows.mjs +35 -0
- package/src/js/findTouchedDup.mjs +78 -0
- package/src/js/fromAudit.mjs +417 -0
- package/src/js/getAddImpact.mjs +197 -0
- package/src/js/getAuthWarn.mjs +391 -0
- package/src/js/getCascadePreview.mjs +186 -0
- package/src/js/getDelRefCount.mjs +138 -0
- package/src/js/getEffMode.mjs +20 -0
- package/src/js/getKpLossy.mjs +44 -49
- package/src/js/getNameErrs.mjs +194 -0
- package/src/js/getOwn.mjs +1 -1
- package/src/js/getPropKey.mjs +37 -0
- package/src/js/isAuthMode.mjs +2 -2
- package/src/js/pairRowsToSnapshot.mjs +84 -0
- package/src/js/refAudit.mjs +287 -0
- package/src/js/refCascade.mjs +500 -0
- package/src/js/relDialogSend.mjs +127 -0
- package/src/js/rowDigest.mjs +193 -0
- package/src/plugins/dataReloadGuard.mjs +37 -0
- package/src/plugins/mShare.mjs +144 -140
- package/src/plugins/mUI.mjs +3 -8
- package/src/schema/tables/users.mjs +152 -152
- package/dist/css/app.6918ec45.css +0 -1
- package/dist/js/app-legacy.7cb7e394.js +0 -1
- package/dist/js/app.7cb7e394.js +0 -1
- package/docs/src_js_getKeyOrderLost.mjs.html +0 -121
- package/docs/src_js_getKpWriteLost.mjs.html +0 -100
- package/docs/src_js_keepKeyOrder.mjs.html +0 -139
- package/src/js/getKeyOrderLost.mjs +0 -49
- package/src/js/getKpWriteLost.mjs +0 -28
- package/src/js/keepKeyOrder.mjs +0 -67
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>src/js/fromAudit.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/fromAudit.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import { isExternalRow } from './externalRows.mjs'
|
|
49
|
+
import { REF_OF, planRefChanges } from './refCascade.mjs'
|
|
50
|
+
import { getDelImpact } from './getDelRefCount.mjs'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
//來源欄 from 之稽核、型別正規化與「整來源清除」規劃(run07 升版維運工具之核心; F-23 外部列受保護、規則帳本 R-PRINCIPLE-00 原則 5):
|
|
54
|
+
// 外部列=from 為非空字串者(externalRows.isExternalRow, 單一擁有者, 本檔不另寫判準); from 為 ''、缺欄、非字串(null/數字/布林/物件)者為後台列.
|
|
55
|
+
// 升版後外部列於後台唯讀(不得改、不得刪), 故升版前舊資料之「非空 from」列將成為唯讀; 非字串 from 後台寫入起被拒收.
|
|
56
|
+
//本檔為純函數(不依賴 Vue/DOM/ORM/fs), 供離線維運腳本 g_fixFromType.mjs/g_purgeFrom.mjs(經 server/tools/fromTools.mjs)與單元測試共用;
|
|
57
|
+
//所有函數不改輸入; 輸出之 id 與名稱僅供離線工具列印於 stdout, 不得寫入 srLog(規則帳本: 日誌不記 id/個資).
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
//TABLE_ORDER: 四表依引用鏈序(被引用者先、引用端後; 同 server/keyCheck.mjs 之 TAB_LOCK_RANK): 標的 → 權限 → 群組 → 使用者
|
|
61
|
+
const TABLE_ORDER = ['targets', 'pemis', 'grups', 'users']
|
|
62
|
+
|
|
63
|
+
//NON_STRING_KINDS: 非字串 from 之型別分類(null/number/boolean/object/array/other)
|
|
64
|
+
const NON_STRING_KINDS = ['null', 'number', 'boolean', 'object', 'array', 'other']
|
|
65
|
+
|
|
66
|
+
//KIND_ORDER: 稽核結果內 from 值分類之固定輸出次序(empty: ''、missing: 缺欄(視同 '')、非字串各型、notObject: 列本身非物件, 最後為非空字串依值分組)
|
|
67
|
+
const KIND_ORDER = ['empty', 'missing', ...NON_STRING_KINDS, 'notObject']
|
|
68
|
+
|
|
69
|
+
//IMPACT_KIND: 清除之表 → getDelImpact 之種類(AND 引用者數之口徑, 同清單頁刪除確認框); targets 之 crules 無 mode、users 無引用端, 不適用
|
|
70
|
+
const IMPACT_KIND = { grups: 'Grup', pemis: 'Pemi' }
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
let isStr = (v) => typeof v === 'string'
|
|
74
|
+
let hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k)
|
|
75
|
+
let isObj = (v) => v !== null && typeof v === 'object'
|
|
76
|
+
let arr = (v) => (Array.isArray(v) ? v : [])
|
|
77
|
+
let hasId = (r) => isObj(r) && isStr(r.id) && r.id !== '' //可寫回/可刪除之列須有非空字串主鍵(ORM 以字串主鍵定位)
|
|
78
|
+
let strCmp = (a, b) => (a < b ? -1 : (a > b ? 1 : 0)) //依 UTF-16 碼元之確定性比較(不隨 locale)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
//fromKind: 列之 from 值分類 { kind, from? }; 非空字串之判準經 isExternalRow(單一擁有者), from 僅 kind 為 'string' 時給
|
|
82
|
+
let fromKind = (row) => {
|
|
83
|
+
if (!isObj(row)) {
|
|
84
|
+
return { kind: 'notObject' }
|
|
85
|
+
}
|
|
86
|
+
if (isExternalRow(row)) {
|
|
87
|
+
return { kind: 'string', from: row.from }
|
|
88
|
+
}
|
|
89
|
+
let v = row.from
|
|
90
|
+
if (v === undefined) {
|
|
91
|
+
return { kind: 'missing' }
|
|
92
|
+
}
|
|
93
|
+
if (v === '') {
|
|
94
|
+
return { kind: 'empty' }
|
|
95
|
+
}
|
|
96
|
+
if (v === null) {
|
|
97
|
+
return { kind: 'null' }
|
|
98
|
+
}
|
|
99
|
+
if (typeof v === 'number') {
|
|
100
|
+
return { kind: 'number' }
|
|
101
|
+
}
|
|
102
|
+
if (typeof v === 'boolean') {
|
|
103
|
+
return { kind: 'boolean' }
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(v)) {
|
|
106
|
+
return { kind: 'array' }
|
|
107
|
+
}
|
|
108
|
+
if (typeof v === 'object') {
|
|
109
|
+
return { kind: 'object' }
|
|
110
|
+
}
|
|
111
|
+
return { kind: 'other' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
//newKinds: 非字串各型之計數容器
|
|
116
|
+
let newKinds = () => Object.fromEntries(NON_STRING_KINDS.map((k) => [k, 0]))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 稽核四表 from 欄之分佈(純函數, 不讀寫任何資料)
|
|
121
|
+
*
|
|
122
|
+
* from 值分類: ''(empty)、缺欄(missing, 視同 '')、非字串(null/number/boolean/object/array/other, 各記型別)、非空字串(string, 依值分組, 精確 ===, 不 trim、不分大小寫);
|
|
123
|
+
* 列本身非物件者記 notObject. 非空字串者為「升版後將成為外部列(唯讀)」, 列入 external.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} d 輸入 { users, grups, pemis, targets }(各為列陣列, 缺者視為空)
|
|
126
|
+
* @returns {Object} 回傳 { entries, external, counts }:
|
|
127
|
+
* entries [{ table, kind, n }](kind 為 'string' 者另帶 from 值), 依 TABLE_ORDER 與 KIND_ORDER 輸出, 非空字串依 from 值升冪; 只列 n 大於 0 者
|
|
128
|
+
* external [{ table, from, n }] 升版後將成為外部列(唯讀)之(表, from)與筆數
|
|
129
|
+
* counts { nRows, nEmpty, nMissing, nNonString, nExternal, nNotObject, nSources, byTable: { 表: { nRows, nEmpty, nMissing, nNonString, nExternal, nNotObject, nSources, kinds } } }
|
|
130
|
+
* @example
|
|
131
|
+
*
|
|
132
|
+
* let r = auditFrom({ users: [{ id: 'u1', from: '' }, { id: 'u2', from: 'pd' }, { id: 'u3', from: null }] })
|
|
133
|
+
* console.log(r.external, r.counts.nNonString)
|
|
134
|
+
* // => [ { table: 'users', from: 'pd', n: 1 } ] 1
|
|
135
|
+
*
|
|
136
|
+
*/
|
|
137
|
+
function auditFrom(d) {
|
|
138
|
+
let data = isObj(d) ? d : {}
|
|
139
|
+
let entries = []
|
|
140
|
+
let external = []
|
|
141
|
+
let byTable = {}
|
|
142
|
+
let counts = { nRows: 0, nEmpty: 0, nMissing: 0, nNonString: 0, nExternal: 0, nNotObject: 0, nSources: 0, byTable }
|
|
143
|
+
|
|
144
|
+
for (let t of TABLE_ORDER) {
|
|
145
|
+
let rows = arr(data[t])
|
|
146
|
+
let tab = { nRows: rows.length, nEmpty: 0, nMissing: 0, nNonString: 0, nExternal: 0, nNotObject: 0, nSources: 0, kinds: newKinds() }
|
|
147
|
+
let nOf = new Map() //kind → 筆數
|
|
148
|
+
let srcs = new Map() //from 值 → 筆數(Map: 值可為 __proto__ 等任意字串)
|
|
149
|
+
for (let row of rows) {
|
|
150
|
+
let k = fromKind(row)
|
|
151
|
+
if (k.kind === 'string') {
|
|
152
|
+
srcs.set(k.from, (srcs.get(k.from) || 0) + 1)
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
nOf.set(k.kind, (nOf.get(k.kind) || 0) + 1)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (let kind of KIND_ORDER) {
|
|
159
|
+
let n = nOf.get(kind) || 0
|
|
160
|
+
if (n === 0) {
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
entries.push({ table: t, kind, n })
|
|
164
|
+
if (kind === 'empty') {
|
|
165
|
+
tab.nEmpty = n
|
|
166
|
+
}
|
|
167
|
+
else if (kind === 'missing') {
|
|
168
|
+
tab.nMissing = n
|
|
169
|
+
}
|
|
170
|
+
else if (kind === 'notObject') {
|
|
171
|
+
tab.nNotObject = n
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
tab.kinds[kind] = n
|
|
175
|
+
tab.nNonString += n
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (let from of [...srcs.keys()].sort(strCmp)) {
|
|
179
|
+
let n = srcs.get(from)
|
|
180
|
+
entries.push({ table: t, kind: 'string', from, n })
|
|
181
|
+
external.push({ table: t, from, n })
|
|
182
|
+
tab.nExternal += n
|
|
183
|
+
tab.nSources++
|
|
184
|
+
}
|
|
185
|
+
byTable[t] = tab
|
|
186
|
+
for (let key of ['nRows', 'nEmpty', 'nMissing', 'nNonString', 'nExternal', 'nNotObject', 'nSources']) {
|
|
187
|
+
counts[key] += tab[key]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { entries, external, counts }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 規劃「把非字串 from 正規化為 ''」(g_fixFromType 之核心; 純函數, 不改輸入)
|
|
196
|
+
*
|
|
197
|
+
* 待改列: from 為 null/數字/布林/物件/陣列等非字串者, 改為 ''(後台列; 與缺欄、'' 同為後台列, 故不改變任何列是否為外部列).
|
|
198
|
+
* 缺欄視同 ''(已是後台列), 不改, 只列出其 id; 非空字串為外部列, 不動; 缺非空字串主鍵而無從寫回者列入 noId(不改, 須人工處理).
|
|
199
|
+
*
|
|
200
|
+
* @param {Array} rows 輸入某一表之全部列
|
|
201
|
+
* @returns {Object} 回傳 { changes, missing, noId, counts }:
|
|
202
|
+
* changes [{ id, kind, before, after }] 待改列, after 恆為 ''; before 為原 from 值(原樣, 同輸入之參考)
|
|
203
|
+
* missing 缺 from 欄之列之 id 陣列(只列出, 不改)
|
|
204
|
+
* noId [{ idx, kind }] 非字串 from 而缺非空字串主鍵者(idx 為其於輸入陣列之索引)
|
|
205
|
+
* counts { nRows, nEmpty, nMissing, nNonString, nExternal, nNotObject, nNoId, kinds }
|
|
206
|
+
* @example
|
|
207
|
+
*
|
|
208
|
+
* let r = planFixFromType([{ id: 'a', from: null }, { id: 'b', from: 'pd' }, { id: 'c' }])
|
|
209
|
+
* console.log(r.changes, r.missing)
|
|
210
|
+
* // => [ { id: 'a', kind: 'null', before: null, after: '' } ] [ 'c' ]
|
|
211
|
+
*
|
|
212
|
+
*/
|
|
213
|
+
function planFixFromType(rows) {
|
|
214
|
+
let src = arr(rows)
|
|
215
|
+
let res = { changes: [], missing: [], noId: [], counts: { nRows: src.length, nEmpty: 0, nMissing: 0, nNonString: 0, nExternal: 0, nNotObject: 0, nNoId: 0, kinds: newKinds() } }
|
|
216
|
+
let c = res.counts
|
|
217
|
+
src.forEach((row, idx) => {
|
|
218
|
+
let k = fromKind(row)
|
|
219
|
+
if (k.kind === 'empty') {
|
|
220
|
+
c.nEmpty++
|
|
221
|
+
}
|
|
222
|
+
else if (k.kind === 'string') {
|
|
223
|
+
c.nExternal++
|
|
224
|
+
}
|
|
225
|
+
else if (k.kind === 'notObject') {
|
|
226
|
+
c.nNotObject++
|
|
227
|
+
}
|
|
228
|
+
else if (k.kind === 'missing') {
|
|
229
|
+
c.nMissing++
|
|
230
|
+
if (hasId(row)) {
|
|
231
|
+
res.missing.push(row.id)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
c.nNonString++
|
|
236
|
+
c.kinds[k.kind]++
|
|
237
|
+
if (hasId(row)) {
|
|
238
|
+
res.changes.push({ id: row.id, kind: k.kind, before: row.from, after: '' })
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
c.nNoId++
|
|
242
|
+
res.noId.push({ idx, kind: k.kind })
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
return res
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
//rowName: 列之顯示名稱(僅供離線輸出; targets 之 id 即名稱; users 不輸出名稱——姓名為個資, 同 authRefsAudit 不回顯 email/name 之慣例)
|
|
251
|
+
let rowName = (keyTable, r) => {
|
|
252
|
+
if (keyTable === 'users') {
|
|
253
|
+
return undefined
|
|
254
|
+
}
|
|
255
|
+
if (keyTable === 'targets') {
|
|
256
|
+
return isStr(r.id) ? r.id : ''
|
|
257
|
+
}
|
|
258
|
+
return isStr(r.name) ? r.name : ''
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
//similarFroms: 與指定 from 不相同但「去前後空白並忽略大小寫後相同」之來源(僅提示, 不納入清除; 防手誤, 如 'PD' 與 'pd'、'pd ' 與 'pd')
|
|
263
|
+
let similarFroms = (rows, from) => {
|
|
264
|
+
let want = from.trim().toLowerCase()
|
|
265
|
+
let m = new Map()
|
|
266
|
+
for (let r of rows) {
|
|
267
|
+
if (isExternalRow(r) && r.from !== from && r.from.trim().toLowerCase() === want) {
|
|
268
|
+
m.set(r.from, (m.get(r.from) || 0) + 1)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return [...m.keys()].sort(strCmp).map((f) => ({ from: f, n: m.get(f) }))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 規劃「整來源清除」: 某表 from 精確等於指定值之全部列, 連同同一次清理引用之計畫(g_purgeFrom 之核心; 純函數, 不改輸入, 不讀寫任何資料)
|
|
277
|
+
*
|
|
278
|
+
* 被清除列 = 該表 `row.from === from`(精確 ===, 不 trim、不忽略大小寫、不模糊); from 須為非空字串, 拒絕 ''、非字串(err 'invalidFrom'); 表名須為 targets/pemis/grups/users(err 'invalidTable').
|
|
279
|
+
* 被清除列缺非空字串主鍵而無從刪除者整體拒絕(err 'purgeRowNoId', 零寫入, 不做部分清除).
|
|
280
|
+
* targets/pemis/grups 之清除另以 refCascade.planRefChanges 規劃「同一次清理引用」(引用端 pemis.crules/grups.cpemis/users.cgrups; rowsNew = 該表扣除被清除列, holdersOld 為引用端全表),
|
|
281
|
+
* 效果同後台刪除該些列(身分以主鍵 id; 歷史同名仍有存續列者不清理); 計畫有 err(如 renameCascadeUnsupported: 引用端含無法安全重新編碼之內容)則整體拒絕、零寫入.
|
|
282
|
+
* users 之清除無引用清理.
|
|
283
|
+
*
|
|
284
|
+
* @param {Object} p 輸入參數物件
|
|
285
|
+
* @param {String} p.keyTable 輸入被清除之表('targets'、'pemis'、'grups' 或 'users')
|
|
286
|
+
* @param {String} p.from 輸入來源名稱(非空字串, 精確比對)
|
|
287
|
+
* @param {Object} p.tables 輸入 { users, grups, pemis, targets }(各為該表全部列; 引用端表須一併給)
|
|
288
|
+
* @returns {Object} 回傳 { err, keyTable, from, holder, field, rows, delIds, nRows, nAdmin, nNoId, changes, delNames, nHolders, nDelHolders, nExternal, impact, similar }:
|
|
289
|
+
* err null 或拒絕原因鍵(invalidTable/invalidFrom/purgeRowNoId/planRefChanges 之 err, 如 renameCascadeUnsupported); 非 null 時 changes 為空(零寫入)
|
|
290
|
+
* rows [{ id, name? }] 被清除列(id 與名稱僅供離線輸出; users 無 name); delIds 為其 id 陣列; nRows 為其筆數; nAdmin 為其中 isAdmin 為 'y' 者(僅 users)
|
|
291
|
+
* changes 引用端改寫 [{ id, field, before, after, nRename, nDel, from }](依 planRefChanges); holder/field 為引用端表與欄位(users 清除為空字串)
|
|
292
|
+
* delNames 須清理之被清除名稱(planRefChanges.delNames); nHolders 為須改寫之引用端列數; nDelHolders 為受刪除影響之列數; nExternal 為其中來源非空者
|
|
293
|
+
* impact { nRefs, nAnd, nWiden }: nRefs 同 nDelHolders; nAnd 為以 AND 引用者(同清單頁刪除確認框 getDelImpact; targets/users 不適用恆 0); nWiden 為歷史同名只刪其一致授權可能變多者
|
|
294
|
+
* similar [{ from, n }] 與指定 from 去空白忽略大小寫後相同而不相同之來源(僅提示, 不清除)
|
|
295
|
+
* @example
|
|
296
|
+
*
|
|
297
|
+
* let r = planPurgeFrom({ keyTable: 'grups', from: 'pd', tables: {
|
|
298
|
+
* users: [{ id: 'u1', cgrups: '{"A":{"mode":"OR","isActive":"y"}}' }],
|
|
299
|
+
* grups: [{ id: 'g1', name: 'A', from: 'pd' }],
|
|
300
|
+
* } })
|
|
301
|
+
* console.log(r.nRows, r.changes[0].after)
|
|
302
|
+
* // => 1 {}
|
|
303
|
+
*
|
|
304
|
+
*/
|
|
305
|
+
function planPurgeFrom(p) {
|
|
306
|
+
let keyTable = isObj(p) ? p.keyTable : ''
|
|
307
|
+
let from = isObj(p) ? p.from : undefined
|
|
308
|
+
let plan = {
|
|
309
|
+
err: null,
|
|
310
|
+
keyTable,
|
|
311
|
+
from,
|
|
312
|
+
holder: '',
|
|
313
|
+
field: '',
|
|
314
|
+
rows: [],
|
|
315
|
+
delIds: [],
|
|
316
|
+
nRows: 0,
|
|
317
|
+
nAdmin: 0,
|
|
318
|
+
nNoId: 0,
|
|
319
|
+
changes: [],
|
|
320
|
+
delNames: [],
|
|
321
|
+
nHolders: 0,
|
|
322
|
+
nDelHolders: 0,
|
|
323
|
+
nExternal: 0,
|
|
324
|
+
impact: { nRefs: 0, nAnd: 0, nWiden: 0 },
|
|
325
|
+
similar: [],
|
|
326
|
+
}
|
|
327
|
+
if (!TABLE_ORDER.includes(keyTable)) {
|
|
328
|
+
plan.err = 'invalidTable'
|
|
329
|
+
return plan
|
|
330
|
+
}
|
|
331
|
+
if (!isStr(from) || from === '') {
|
|
332
|
+
plan.err = 'invalidFrom'
|
|
333
|
+
return plan
|
|
334
|
+
}
|
|
335
|
+
let tables = isObj(p.tables) ? p.tables : {}
|
|
336
|
+
let rowsOld = arr(tables[keyTable])
|
|
337
|
+
let isHit = (r) => isObj(r) && r.from === from //精確 ===: from 為非空字串, 故命中者必為外部列
|
|
338
|
+
let hit = rowsOld.filter(isHit)
|
|
339
|
+
plan.similar = similarFroms(rowsOld, from)
|
|
340
|
+
plan.nRows = hit.length
|
|
341
|
+
plan.nNoId = hit.filter((r) => !hasId(r)).length
|
|
342
|
+
plan.rows = hit.filter(hasId).map((r) => {
|
|
343
|
+
let o = { id: r.id }
|
|
344
|
+
let name = rowName(keyTable, r)
|
|
345
|
+
if (name !== undefined) {
|
|
346
|
+
o.name = name
|
|
347
|
+
}
|
|
348
|
+
return o
|
|
349
|
+
})
|
|
350
|
+
plan.delIds = plan.rows.map((r) => r.id)
|
|
351
|
+
if (keyTable === 'users') {
|
|
352
|
+
plan.nAdmin = hit.filter((r) => r.isAdmin === 'y').length
|
|
353
|
+
}
|
|
354
|
+
let ref = hasOwn(REF_OF, keyTable) ? REF_OF[keyTable] : null //users 無引用端
|
|
355
|
+
if (ref !== null) {
|
|
356
|
+
plan.holder = ref.holder
|
|
357
|
+
plan.field = ref.field
|
|
358
|
+
}
|
|
359
|
+
if (plan.nNoId > 0) {
|
|
360
|
+
plan.err = 'purgeRowNoId'
|
|
361
|
+
return plan
|
|
362
|
+
}
|
|
363
|
+
if (hit.length === 0 || ref === null) {
|
|
364
|
+
return plan
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
//引用清理: 與伺服端刪除同一純函數(R-REF-CASCADE-01), rowsNew = 該表扣除被清除列
|
|
368
|
+
let rowsNew = rowsOld.filter((r) => !isHit(r))
|
|
369
|
+
let holders = arr(tables[ref.holder])
|
|
370
|
+
let rp = planRefChanges({ keyTable, rowsOld, rowsNew, holdersOld: holders })
|
|
371
|
+
if (rp.err !== null) {
|
|
372
|
+
plan.err = rp.err
|
|
373
|
+
return plan
|
|
374
|
+
}
|
|
375
|
+
plan.changes = rp.changes
|
|
376
|
+
plan.delNames = rp.delNames
|
|
377
|
+
plan.nHolders = rp.nHolders
|
|
378
|
+
plan.nDelHolders = rp.nDelHolders
|
|
379
|
+
plan.nExternal = rp.nExternal
|
|
380
|
+
plan.impact.nRefs = rp.nDelHolders
|
|
381
|
+
if (hasOwn(IMPACT_KIND, keyTable)) {
|
|
382
|
+
let im = getDelImpact(plan.delIds, rowsOld, holders, IMPACT_KIND[keyTable], rowsNew)
|
|
383
|
+
plan.impact.nAnd = Math.min(im.nAnd, rp.nDelHolders)
|
|
384
|
+
plan.impact.nWiden = im.nWiden
|
|
385
|
+
}
|
|
386
|
+
return plan
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 套用清除計畫於四表(回傳新之 tables, 不改輸入; 用於 --table all 之逐表模擬與寫後預測): 移除被清除列、引用端欄位換成 after
|
|
392
|
+
*
|
|
393
|
+
* @param {Object} tables 輸入 { users, grups, pemis, targets }
|
|
394
|
+
* @param {Object} plan 輸入 planPurgeFrom 之結果; err 非 null 者原樣回傳(不套用)
|
|
395
|
+
* @returns {Object} 回傳新之 tables(僅被改之表為新陣列, 其餘沿用)
|
|
396
|
+
*/
|
|
397
|
+
function applyPurgePlan(tables, plan) {
|
|
398
|
+
let out = { ...tables }
|
|
399
|
+
if (!isObj(plan) || plan.err !== null) {
|
|
400
|
+
return out
|
|
401
|
+
}
|
|
402
|
+
let del = new Set(plan.delIds)
|
|
403
|
+
out[plan.keyTable] = arr(tables[plan.keyTable]).filter((r) => !(isObj(r) && del.has(r.id) && r.from === plan.from))
|
|
404
|
+
if (plan.holder !== '') {
|
|
405
|
+
let m = new Map(plan.changes.map((c) => [c.id, c]))
|
|
406
|
+
out[plan.holder] = arr(tables[plan.holder]).map((r) => {
|
|
407
|
+
let c = isObj(r) ? m.get(r.id) : undefined
|
|
408
|
+
return c === undefined ? r : { ...r, [c.field]: c.after }
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
return out
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 規劃多表整來源清除(--table all): 依引用鏈序 targets → pemis → grups → users 逐表規劃, 每表以「前面各表套用後」之模擬狀態計畫(與逐表真實寫入之狀態一致)
|
|
417
|
+
*
|
|
418
|
+
* 任一表之計畫有 err 則停止並整體拒絕(err 為該鍵, errTable 為該表; plans 含到失敗表為止之計畫). 純函數, 不改輸入.
|
|
419
|
+
*
|
|
420
|
+
* @param {Object} p 輸入參數物件
|
|
421
|
+
* @param {String} p.from 輸入來源名稱(非空字串, 精確比對)
|
|
422
|
+
* @param {Object} p.tables 輸入 { users, grups, pemis, targets }
|
|
423
|
+
* @param {Array} [p.keyTables=TABLE_ORDER] 輸入要清除之表(可為子集; 一律依 TABLE_ORDER 之序處理並去重)
|
|
424
|
+
* @returns {Object} 回傳 { err, errTable, from, plans, tablesAfter, totals }: tablesAfter 為全部套用後之預測狀態(err 非 null 時無意義);
|
|
425
|
+
* totals 為各表加總 { nRows, nHolders, nDelHolders, nAnd, nWiden, nExternal, nAdmin }
|
|
426
|
+
*/
|
|
427
|
+
function planPurgeChain(p) {
|
|
428
|
+
let from = isObj(p) ? p.from : undefined
|
|
429
|
+
let tables = isObj(p) && isObj(p.tables) ? p.tables : {}
|
|
430
|
+
let want = isObj(p) && Array.isArray(p.keyTables) ? p.keyTables : TABLE_ORDER
|
|
431
|
+
let res = { err: null, errTable: '', from, plans: [], tablesAfter: { ...tables }, totals: { nRows: 0, nHolders: 0, nDelHolders: 0, nAnd: 0, nWiden: 0, nExternal: 0, nAdmin: 0 } }
|
|
432
|
+
if (!isStr(from) || from === '') {
|
|
433
|
+
res.err = 'invalidFrom'
|
|
434
|
+
return res
|
|
435
|
+
}
|
|
436
|
+
if (want.length === 0 || want.some((t) => !TABLE_ORDER.includes(t))) {
|
|
437
|
+
res.err = 'invalidTable'
|
|
438
|
+
return res
|
|
439
|
+
}
|
|
440
|
+
let cur = tables
|
|
441
|
+
for (let t of TABLE_ORDER.filter((x) => want.includes(x))) {
|
|
442
|
+
let plan = planPurgeFrom({ keyTable: t, from, tables: cur })
|
|
443
|
+
res.plans.push(plan)
|
|
444
|
+
if (plan.err !== null) {
|
|
445
|
+
res.err = plan.err
|
|
446
|
+
res.errTable = t
|
|
447
|
+
return res
|
|
448
|
+
}
|
|
449
|
+
res.totals.nRows += plan.nRows
|
|
450
|
+
res.totals.nHolders += plan.nHolders
|
|
451
|
+
res.totals.nDelHolders += plan.nDelHolders
|
|
452
|
+
res.totals.nAnd += plan.impact.nAnd
|
|
453
|
+
res.totals.nWiden += plan.impact.nWiden
|
|
454
|
+
res.totals.nExternal += plan.nExternal
|
|
455
|
+
res.totals.nAdmin += plan.nAdmin
|
|
456
|
+
cur = applyPurgePlan(cur, plan)
|
|
457
|
+
}
|
|
458
|
+
res.tablesAfter = cur
|
|
459
|
+
return res
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
export { TABLE_ORDER, NON_STRING_KINDS, KIND_ORDER, fromKind, auditFrom, planFixFromType, planPurgeFrom, applyPurgePlan, planPurgeChain }
|
|
464
|
+
export default auditFrom
|
|
465
|
+
</code></pre>
|
|
466
|
+
</article>
|
|
467
|
+
</section>
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
<br class="clear">
|
|
477
|
+
|
|
478
|
+
<footer>
|
|
479
|
+
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.
|
|
480
|
+
</footer>
|
|
481
|
+
|
|
482
|
+
<script>prettyPrint();</script>
|
|
483
|
+
<script src="scripts/polyfill.js"></script>
|
|
484
|
+
<script src="scripts/linenumber.js"></script>
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
</body>
|
|
489
|
+
</html>
|