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.
- package/README.md +158 -117
- 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 +24747 -832
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +245 -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 +15 -4
- package/docs/server_wrapInjected.mjs.html +3 -3
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_js_cvKpToArr.mjs.html +125 -0
- package/docs/src_js_cvKpToObj.mjs.html +210 -0
- 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 +95 -0
- 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 +155 -0
- 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 +92 -0
- package/docs/src_js_joinNames.mjs.html +96 -0
- 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 +243 -30
- package/server/cascadeWrite.mjs +153 -0
- package/server/keyCheck.mjs +281 -0
- package/server/procCore.mjs +178 -39
- package/server/procLang.mjs +271 -0
- 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/validateRows.mjs +13 -2
- package/server/wrapInjected.mjs +1 -1
- package/src/components/LayoutContentGrups.vue +399 -77
- package/src/components/LayoutContentPemis.vue +400 -78
- package/src/components/LayoutContentTargets.vue +261 -25
- package/src/components/LayoutContentUsers.vue +336 -98
- package/src/components/VeCgrups.vue +119 -115
- package/src/components/VeCpemis.vue +117 -115
- package/src/components/VeCrules.vue +18 -2
- package/src/components/VeGrupBlngUsers.vue +240 -73
- package/src/components/VePemiBlngGrups.vue +247 -83
- package/src/js/cvKpToArr.mjs +53 -0
- package/src/js/cvKpToObj.mjs +138 -0
- 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/fmtLossyMsg.mjs +23 -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 +83 -0
- 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 +20 -0
- package/src/js/joinNames.mjs +24 -0
- 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 +147 -174
- 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.963e800c.js +0 -1
- package/dist/js/app.963e800c.js +0 -1
package/docs/index.html
CHANGED
|
@@ -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#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#eachOwn">eachOwn</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#getOwn">getOwn</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</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>
|
|
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
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<br class="clear">
|
|
72
72
|
|
|
73
73
|
<footer>
|
|
74
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
74
|
+
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.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<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#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#eachOwn">eachOwn</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#getOwn">getOwn</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</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>
|
|
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
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -84,7 +84,11 @@ import procStaInfor from './procStaInfor.mjs'
|
|
|
84
84
|
import { maskTok, maskQuery, maskUrl } from './maskLog.mjs'
|
|
85
85
|
import wrapInjected from './wrapInjected.mjs'
|
|
86
86
|
import validateRowsBeforeWrite from './validateRows.mjs'
|
|
87
|
+
import planRefChanges, { REF_OF, diffRefTargets } from '../src/js/refCascade.mjs'
|
|
88
|
+
import { findDupIndex, findKeyOtherFrom, withTabLocks, mapKeyOf, checkMapKeyBatch, pickUniqueUser } from './keyCheck.mjs'
|
|
89
|
+
import { checkCascadePlan, writeWithCascade, buildSyncRowsNew } from './cascadeWrite.mjs'
|
|
87
90
|
import { createAuthorizeExec, wrapOperOrm } from './dataChannel.mjs'
|
|
91
|
+
import { markHasKey, keepAssignOnMissing, keepAssignByEntity, countIdRenumber } from './syncKeep.mjs'
|
|
88
92
|
|
|
89
93
|
|
|
90
94
|
/**
|
|
@@ -104,7 +108,9 @@ import { createAuthorizeExec, wrapOperOrm } from './dataChannel.mjs'
|
|
|
104
108
|
* @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
|
|
105
109
|
* @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
|
|
106
110
|
* @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
|
|
107
|
-
* @param {String} [opt.mappingBy='id'] 輸入外部系統識別使用者token後所提供之資料物件,與權限系統之使用者資料物件,兩者間查找之對應欄位,可選'id'、'email'、'name',未給或非此三者時為'id',預設'id'
|
|
111
|
+
* @param {String} [opt.mappingBy='id'] 輸入外部系統識別使用者token後所提供之資料物件,與權限系統之使用者資料物件,兩者間查找之對應欄位,可選'id'、'email'、'name',未給或非此三者時為'id',預設'id'。對應鍵須能唯一指出使用者:mappingBy為'email'或'name'時,外部同步(syncAndReplaceTabs)users之有效(isActive為'y')列,該欄位須為非空字串且與本批及其他from之有效列皆不重複(違規整批拒收invalidRows);登入查找以對應鍵於權限系統users命中多於1筆有效使用者時一律拒絕(ambiguousUserFromPerm),不取首筆;mappingBy為'id'(預設)時不另檢查
|
|
112
|
+
* @param {Boolean} [opt.cascadeRename=true] 輸入群組、權限改名時是否連帶更新引用它之授權字串(users.cgrups、grups.cpemis),預設true。改名以主鍵id判定(同id異名),後台整表儲存與外部同步(同一from內)皆適用,於同一次寫入內改寫並可能寫入外部來源之users列之cgrups;給false則不連帶(引用成為懸空,視同引用查無對象而收嚴並於頁面提示)。群組、權限、標的被刪除時之引用連帶清理(users.cgrups、grups.cpemis、pemis.crules)不受此設定影響
|
|
113
|
+
* @param {String} [opt.lockExternalRows='y'] 輸入後台是否不得改外部列(from為非空字串,即經syncAndReplaceTabs由外部來源同步之列),可選'y'或'n',預設'y'。'y'時伺服端強制:後台新增列from須為空字串、外部列不可刪除、不可改from、身分/結構欄(指派欄與順序以外之欄位)不可改,違規整次拒收(saveFromNotAllowed/saveExternalRowChanged);授權指派欄(users.cgrups、grups.cpemis、pemis.crules)後台可寫,但須附載入基準(用戶端自動附帶),無基準者拒收(saveBaselineRequired);外部列之order由外部擁有、後台儲存不重編。給'n'回到舊行為(僅保留from須為字串之檢查)。升版後既有庫中from為非空字串之列即視為外部列而唯讀,請先以g_fixFromType.mjs/g_purgeFrom.mjs盤點(見README Upgrade Notes)
|
|
108
114
|
* @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串,預設'./logs'
|
|
109
115
|
* @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
|
|
110
116
|
* @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
|
|
@@ -297,6 +303,14 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
297
303
|
// console.log('mappingBy', mappingBy)
|
|
298
304
|
|
|
299
305
|
|
|
306
|
+
//cascadeRename, 群組/權限改名時是否連帶更新引用它之授權字串(預設 true; 僅 false 關閉; 刪除連帶清理不受此設定影響, R-REF-CASCADE-01)
|
|
307
|
+
let cascadeRename = get(opt, 'cascadeRename', true) !== false
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
//lockExternalRows, 後台是否不得改外部列(from 為非空字串者)之身分/結構欄、不得刪除與複製繼承 from(預設 true; 僅 'n' 或 false 關閉, 回到舊行為; F-23, R-EXT-OWN-01, ADR-037)
|
|
311
|
+
let lockExternalRows = !(get(opt, 'lockExternalRows', 'y') === 'n' || get(opt, 'lockExternalRows', 'y') === false)
|
|
312
|
+
|
|
313
|
+
|
|
300
314
|
//kpLangExt
|
|
301
315
|
let kpLangExt = get(opt, 'kpLangExt', null)
|
|
302
316
|
|
|
@@ -327,7 +341,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
327
341
|
//部署方常以 w-web-sso 對外 helper(≤1.1.4)包裝 getUserByToken, 其 reject 字串含已代入系統介接權杖與使用者權杖之完整網址.
|
|
328
342
|
//包裝後失敗視同否定結果(getUserByToken → null 等同查無, verify* → false 等同無權限), 走各呼叫點既有分支回自家 key;
|
|
329
343
|
//上游原文不回前端、不入 log, 只於此記 warn(白名單 key, 或型別與 Error.name). 同一次失敗 log 2~3 筆: 此處 warn(因) + 呼叫點 error(果).
|
|
330
|
-
//以下取代後, 本函數內除此三行外不得再直接使用原始參數(盤點見
|
|
344
|
+
//以下取代後, 本函數內除此三行外不得再直接使用原始參數(盤點見 sgud/rule/rulebook.md「權杖不外洩」, 測試 unit-wrapInjected WRAP-SITE-*)
|
|
331
345
|
let logInjectFail = (name) => {
|
|
332
346
|
return (d) => {
|
|
333
347
|
srLog.warn({ event: `inject-${name}-fail`, ...d })
|
|
@@ -352,10 +366,25 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
352
366
|
|
|
353
367
|
|
|
354
368
|
//pmKeyMutex: per-key in-memory mutex, 同 key 序列化、不同 key 並行.
|
|
355
|
-
//perm 為「整表批次 diff」寫入, key
|
|
369
|
+
//perm 為「整表批次 diff」寫入, key 粒度為整表: tabWriteKey(keyTable)(= 'tabWrite:' + keyTable), 外部同步與後台整表寫入共用同鍵, 防同表並行 race (lost update) 與唯一性檢查被交錯略過 (R-WRITE-LOCK-01).
|
|
356
370
|
let kmx = pmKeyMutex()
|
|
357
371
|
|
|
358
372
|
|
|
373
|
+
//syncWarns: 外部同步「同一 from 同名異 id」(疑似 id 重編)之提示登錄(記憶體, 重啟即清; 主控定案: 不加開關, 記 warn log 並使頁面可提示):
|
|
374
|
+
//以「表名+來源」為鍵, 最近一次同步有此情形者登錄筆數與時間, 已無此情形者清除; 只含表名、來源名稱、筆數、時間, 不含個資
|
|
375
|
+
let kpSyncWarn = new Map()
|
|
376
|
+
let setSyncWarn = (keyTable, from, n) => {
|
|
377
|
+
let k = `${keyTable}\u0000${from}`
|
|
378
|
+
if (n > 0) {
|
|
379
|
+
kpSyncWarn.set(k, { keyTable, from, n, time: new Date().toISOString() })
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
kpSyncWarn.delete(k)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
let getSyncWarns = () => [...kpSyncWarn.values()]
|
|
386
|
+
|
|
387
|
+
|
|
359
388
|
//lockSave: 後台整表寫入(updateTabItems)之雙擊防護(後端, 見 lockSave.mjs, ADR-025): 以「updateTabItems:{表名}:{操作者 id}」於 cacheSt 原子占位,
|
|
360
389
|
//同一操作者之同表寫入處理中再送出即 reject 'saveInProgress'(不排隊); 包在 kmx 之外層, 不同操作者之並行仍由 kmx 序列化(ADR-012)
|
|
361
390
|
let cst = cacheSt()
|
|
@@ -363,7 +392,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
363
392
|
|
|
364
393
|
|
|
365
394
|
//procCore: 整表批次 diff 寫入邏輯 (updateTabItems + updateTargets/Pemis/Grups/Users)
|
|
366
|
-
let pc = procCore(woItems, procOrm, { srLog, kmx, lockSave })
|
|
395
|
+
let pc = procCore(woItems, procOrm, { srLog, kmx, lockSave, mappingBy, cascadeRename, lockExternalRows }) //mappingBy 供 users 整表寫入的對應鍵檢查(R-PRINCIPLE-00 適用表 h); cascadeRename 供群組/權限改名之引用級聯(適用表 m); lockExternalRows 供後台對外部列之守門(F-23)
|
|
367
396
|
|
|
368
397
|
|
|
369
398
|
//procStaInfor: 統計資訊, fdLog 對齊 srLog 使用同一目錄(讀 settings.logFd, fallback 邏輯同 srLog.mjs); mock 由 settings.staEventMock 注入(e2e 統計圖穩定用)
|
|
@@ -400,6 +429,10 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
400
429
|
modeEditGrups,
|
|
401
430
|
modeEditUsers,
|
|
402
431
|
|
|
432
|
+
cascadeRename, //改名是否連帶更新引用(opt.cascadeRename), 供清單頁儲存前預覽與伺服端一致(R-REF-CASCADE-01)
|
|
433
|
+
lockExternalRows: lockExternalRows ? 'y' : 'n', //後台是否不得改外部列(opt.lockExternalRows), 供清單頁逐列唯讀、複製重置 from 與伺服端一致(R-EXT-OWN-01)
|
|
434
|
+
syncWarns: getSyncWarns(), //外部同步疑似 id 重編之提示(同一 from 同名異 id), 供清單頁橫幅(原則 3: 提示); 不含個資, 僅表名、來源名稱、筆數、時間
|
|
435
|
+
|
|
403
436
|
widthTargetId,
|
|
404
437
|
widthTargetDescription,
|
|
405
438
|
widthPemisName,
|
|
@@ -481,11 +514,18 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
481
514
|
return Promise.reject(`cannotGetUserProp`)
|
|
482
515
|
}
|
|
483
516
|
|
|
484
|
-
//userFind
|
|
485
|
-
let
|
|
486
|
-
|
|
517
|
+
//userFind, 對應鍵須唯一(F-14, 規則帳本 R-MAPKEY-02): 命中多於 1 筆即拒絕, 不取首筆(取首筆等於由資料庫列序決定登入者為誰)
|
|
518
|
+
let userRs = await woItems.users.select({ [mappingBy]: vSelf, isActive: 'y' })
|
|
519
|
+
let { user: userFind, count: countFind } = pickUniqueUser(userRs)
|
|
487
520
|
// console.log('userFind', userFind)
|
|
488
521
|
|
|
522
|
+
//check, 多筆: 只記 mappingBy 與筆數, 不記使用者之對應值(email/name 為個資); 0 筆行為不變
|
|
523
|
+
if (countFind > 1) {
|
|
524
|
+
console.log('mappingBy', mappingBy, 'count', countFind)
|
|
525
|
+
srLog.error({ event: 'getTokenUser-error', msg: 'ambiguousUserFromPerm', mappingBy, count: countFind })
|
|
526
|
+
return Promise.reject(`ambiguousUserFromPerm`)
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
//check
|
|
490
530
|
if (!iseobj(userFind)) {
|
|
491
531
|
console.log('userSelf', userSelf)
|
|
@@ -636,7 +676,9 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
636
676
|
let syncAndReplaceTabs = async (userId, inp, keyTable) => {
|
|
637
677
|
|
|
638
678
|
//序列化同表之並行整表 delAll+insert: 防 lost update (同 keyTable 序列化, 不同表並行)
|
|
639
|
-
|
|
679
|
+
//與後台整表寫入(procCore.updateTabItems)共用同表互斥鍵 tabWriteKey: 兩者各自先檢查名稱/對應鍵唯一後寫入, 鍵不同則交錯可略過 F-02/F-14(外審 sol R10);
|
|
680
|
+
//有改名或刪除而須改寫引用端時, 於內層依階層再取引用端表之鎖(同步群組/權限/標的時, 被刪除或改名者之引用須連帶清理/更新, R-REF-CASCADE-01), 皆經單一取鎖函數(R-WRITE-LOCK-02)
|
|
681
|
+
return await withTabLocks(kmx, [keyTable], async () => {
|
|
640
682
|
|
|
641
683
|
//from
|
|
642
684
|
let from = get(inp, 'from', '')
|
|
@@ -669,6 +711,11 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
669
711
|
return Promise.reject(`invalidRows`)
|
|
670
712
|
}
|
|
671
713
|
|
|
714
|
+
//keyAssign/hasAssign: 後台可寫之指派欄(users.cgrups/grups.cpemis/pemis.crules; targets 無)及各列是否自帶該鍵(須於 ltdtmapping 缺鍵補 '' 之前記錄);
|
|
715
|
+
//缺該鍵者於下方保留庫內指派(F-23, 指派欄屬後台, 見 syncKeep.mjs)
|
|
716
|
+
let keyAssign = (keyTable === 'users') ? 'cgrups' : ((keyTable === 'grups') ? 'cpemis' : ((keyTable === 'pemis') ? 'crules' : null))
|
|
717
|
+
let hasAssign = (keyAssign !== null) ? markHasKey(rows, keyAssign) : null
|
|
718
|
+
|
|
672
719
|
//save from
|
|
673
720
|
rows = map(rows, (v, k) => {
|
|
674
721
|
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
@@ -692,6 +739,40 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
692
739
|
return Promise.reject(`invalidRows`)
|
|
693
740
|
}
|
|
694
741
|
|
|
742
|
+
//名稱/標的 id 須為非空字串(規則帳本 R-SYNC-NAME-01, 主控複審 R2): 比照後台整表寫入之 ckKey(procCore.mjs: isestr(value)),
|
|
743
|
+
//grups/pemis 之 name、targets 之 id 缺漏、空字串、非字串(數字、布林、null)一律整批拒收, 於 delAll 之前, 該 from 與他 from 之既有列不變.
|
|
744
|
+
//why: 修正前 name:'' 之群組/權限可入庫, 伺服端 arrFinder 以 === 採納之(授權有效), 編輯器(cvKpToObj/doSave)視空名稱為無效, 管理者存檔即移除該授權
|
|
745
|
+
//(移除 AND 限制時等於增權); 與後台路徑(ckKey 拒收空名稱)不對稱. users 不在範圍(name 可重複); 不比照 ckKey 之重複偵測(複審範圍僅「非空」)
|
|
746
|
+
let keyName = (keyTable === 'targets') ? 'id' : ((keyTable === 'grups' || keyTable === 'pemis') ? 'name' : null)
|
|
747
|
+
if (keyName !== null) {
|
|
748
|
+
let kName = rows.findIndex((v) => !isestr(get(v, keyName)))
|
|
749
|
+
if (kName >= 0) {
|
|
750
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', reason: (keyName === 'id') ? 'idInvalid' : 'nameInvalid', keyTable, from, row: kName, field: keyName })
|
|
751
|
+
return Promise.reject(`invalidRows`)
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
//群組/權限名稱唯一(F-02, 規則帳本 R-NAME-UNIQ-01, ADR-034), 本批 rows 內 name 精確 === 重複整批拒收(他 from 已使用者見下方 nameOtherFrom), 於 delAll 之前, 該 from 與他 from 之既有列不變.
|
|
756
|
+
//why: 授權以名稱查找(arrFinder 取第一個同名列), 同名並存即授權取決於資料庫列序; 後台 ckKey/updateTabItems 早已拒同名, 外部同步原不拒(ADR-031 決定 4 曾明載不比照), 兩入口不對稱.
|
|
757
|
+
//外部應用只收固定語系鍵 invalidRows(規則帳本 R-ORM-02); targets 之 id 為主鍵(validateRowsBeforeWrite 已查批內重複), users 之 name 允許重複, 皆不在此
|
|
758
|
+
if (keyName === 'name') {
|
|
759
|
+
let kDup = findDupIndex(rows, 'name')
|
|
760
|
+
if (kDup >= 0) {
|
|
761
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', reason: 'nameDuplicate', keyTable, from, row: kDup, field: 'name' })
|
|
762
|
+
return Promise.reject(`invalidRows`)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
//users 對應鍵批內檢查(F-14, 規則帳本 R-MAPKEY-01, ADR-034): mappingBy 為 'email'/'name' 時, 每個 isActive==='y' 之列該欄位須非空字串且批內唯一(精確 ===); 'id'(預設)不另檢查;
|
|
767
|
+
//只有「對應鍵」才是登入查找(getTokenUser)之鍵, 其唯一性才是安全前提——預設部署之 email 可空/可重複, 不得因新規則使其外部同步全數失敗. log 不記對應值(個資)
|
|
768
|
+
if (keyTable === 'users') {
|
|
769
|
+
let mk = checkMapKeyBatch(rows, mappingBy)
|
|
770
|
+
if (mk !== null) {
|
|
771
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', reason: mk.reason, keyTable, from, row: mk.k, field: mappingBy })
|
|
772
|
+
return Promise.reject(`invalidRows`)
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
695
776
|
//寫入前驗證(規則帳本 R-ORM-01, ADR-026): 於 delAll 之前擋下可預判之寫入失敗, 該 from 之既有列不變(ADR-008 防誤清源).
|
|
696
777
|
//why: 先 delAll 再 insert, 而 w-serv-orm 1.1.8 起對「會被 clearXSS 改寫之主鍵」整批拒絕、LMDB 對 > 1978 B 之主鍵整批拒絕,
|
|
697
778
|
//修正前遇之即該 from 之既有列全數清空(2026-10-14 V4/V7 實測). 主鍵問題 → invalidRows; 任一字串欄位含標記 → htmlMarkupNotAllowed
|
|
@@ -736,21 +817,140 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
736
817
|
}
|
|
737
818
|
}
|
|
738
819
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
820
|
+
//跨 from 同名/同對應鍵(F-02/F-14, 規則帳本 R-NAME-UNIQ-01/R-MAPKEY-01): grups/pemis 之 name、users 於 mappingBy 為 email/name 時之該欄位(僅 active 列),
|
|
821
|
+
//已為其他 from 之既有列使用 → 整批拒絕且不進 delAll; 同 from 之既有列不算衝突(隨後由 delAll 重寫). 先 select 後比對, select 失敗之處置同上段(toOutKey, 未寫入任何資料)
|
|
822
|
+
let keyOther = (keyName === 'name') ? 'name' : ((keyTable === 'users') ? mapKeyOf(mappingBy) : null)
|
|
823
|
+
if (keyOther !== null) {
|
|
824
|
+
let rsAll = null
|
|
825
|
+
try {
|
|
826
|
+
rsAll = await woItems[keyTable].select()
|
|
827
|
+
}
|
|
828
|
+
catch (err) {
|
|
829
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
830
|
+
let key = toOutKey(err, 'anUnexpectedErrorOccurred') //尚未寫入任何資料, 不回 saveWriteFail
|
|
831
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from, step: 'select' })
|
|
832
|
+
return Promise.reject(key)
|
|
833
|
+
}
|
|
834
|
+
let kOther = findKeyOtherFrom(rows, rsAll, from, keyOther, { onlyActive: keyTable === 'users' })
|
|
835
|
+
if (kOther >= 0) {
|
|
836
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', reason: (keyTable === 'users') ? 'mapKeyOtherFrom' : 'nameOtherFrom', keyTable, from, row: kOther, field: keyOther })
|
|
837
|
+
return Promise.reject('invalidRows')
|
|
838
|
+
}
|
|
744
839
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
840
|
+
|
|
841
|
+
//外部同步 payload 缺指派欄鍵之列保留庫內值(F-23; 主控定案: 指派欄屬後台, 同步不得清掉後台指派; 含該鍵則以外部為準): 修正前 ltdtmapping 缺鍵補 ''、delAll+insert 即清空後台已指派者.
|
|
842
|
+
//三個指派欄(users.cgrups/grups.cpemis/pemis.crules; run08 起 cpemis/crules 比照 cgrups)皆是; 只在確有缺鍵之列時才讀庫內(同一 from).
|
|
843
|
+
//users: 對應先依 id、無 id 者依 mappingBy(email/name)恰命中 1 筆, 對不到者視為新使用者(維持 ''), 對應鍵矛盾者不保留(keepAssignOnMissing);
|
|
844
|
+
//grups/pemis: 無登入對應鍵, 實體身分依 id(無 id 者依名稱恰 1 筆; 改名而 cascadeRename 為假者不保留; keepAssignByEntity). select 失敗之處置同上段(toOutKey, 尚未寫入任何資料)
|
|
845
|
+
if (hasAssign !== null && hasAssign.some((b) => b === false)) {
|
|
846
|
+
let rsSame = null
|
|
847
|
+
try {
|
|
848
|
+
rsSame = await woItems[keyTable].select({ from }) //此處 keyTable 為 users/grups/pemis(hasAssign 非 null); users 非登入查找, 不經 pickUniqueUser(SITE-KC-004 只管登入反查)
|
|
849
|
+
}
|
|
850
|
+
catch (err) {
|
|
851
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
852
|
+
let key = toOutKey(err, 'anUnexpectedErrorOccurred')
|
|
853
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from, step: 'select' })
|
|
854
|
+
return Promise.reject(key)
|
|
855
|
+
}
|
|
856
|
+
//isKeepable: 保留值於 delAll 之前須再通過一次寫入前驗證(上方 validateRowsBeforeWrite 看到的是補出之空字串, 外審 sol R4); 不過者不保留(視為空, 收嚴), 只記筆數
|
|
857
|
+
let isKeepable = (val) => validateRowsBeforeWrite([{ id: 'keep', [keyAssign]: val }]) === null
|
|
858
|
+
let kr = (keyTable === 'users') ? keepAssignOnMissing({ rows, hasKey: hasAssign, rowsOldSameFrom: rsSame, mappingBy, isKeepable }) : keepAssignByEntity({ rows, hasKey: hasAssign, rowsOldSameFrom: rsSame, key: keyAssign, cascadeRename, isKeepable })
|
|
859
|
+
rows = kr.rows
|
|
860
|
+
if (kr.nKeep > 0) {
|
|
861
|
+
srLog.info({ event: 'syncAndReplaceTabs-keepAssign', keyTable, from, field: keyAssign, n: kr.nKeep })
|
|
862
|
+
}
|
|
863
|
+
if (kr.nSkip > 0) {
|
|
864
|
+
srLog.warn({ event: 'syncAndReplaceTabs-keepAssign-skip', keyTable, from, field: keyAssign, n: kr.nSkip, reason: 'notKeepable' })
|
|
865
|
+
}
|
|
866
|
+
if (kr.nConflict > 0) {
|
|
867
|
+
srLog.warn({ event: 'syncAndReplaceTabs-keepAssign-skip', keyTable, from, field: keyAssign, n: kr.nConflict, reason: (keyTable === 'users') ? 'mapKeyConflict' : 'identityConflict' }) //users: 對應鍵與 id 指向不同使用者; grups/pemis: 同名多筆無從對應或改名而引用不跟著改; 指派不移轉而清空(收嚴須附提示; 不含個資值)
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
//引用連帶(適用表 m/n, R-REF-CASCADE-01): 同步 grups/pemis/targets 時, 同一 from 內依穩定 id 判定——同 id 異名=改名級聯、舊 id 不在本批=刪除而連帶清理引用(寫入後仍存續之同名者不清理);
|
|
872
|
+
//未帶 id 之列(ORM 每次補隨機主鍵)改依名稱對應(見 buildSyncRowsNew). 預檢(新名撞懸空名、歷史同名組改名、容器內相撞、改寫後欄位驗證)於 delAll 之前, 被拒整批零變更;
|
|
873
|
+
//外部應用只收固定語系鍵 invalidRows/htmlMarkupNotAllowed(R-ORM-02), 動態原因入 srLog. select 失敗之處置同上段(toOutKey, 未寫入任何資料).
|
|
874
|
+
//是否須改寫引用端只依本表新舊列判定(diffRefTargets), 持有本表鎖之下即可決定是否再取引用端表之鎖(R-WRITE-LOCK-02); 取齊鎖之後才讀取引用端、規劃與預檢
|
|
875
|
+
let rowsPlan = null
|
|
876
|
+
let kind = null
|
|
877
|
+
if (Object.prototype.hasOwnProperty.call(REF_OF, keyTable)) {
|
|
878
|
+
try {
|
|
879
|
+
let rowsOld = await woItems[keyTable].select()
|
|
880
|
+
rowsPlan = { rowsOld, rowsNew: buildSyncRowsNew(keyTable, rowsOld, rows, from) }
|
|
881
|
+
}
|
|
882
|
+
catch (err) {
|
|
883
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
884
|
+
let key = toOutKey(err, 'anUnexpectedErrorOccurred')
|
|
885
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from, step: 'select' })
|
|
886
|
+
return Promise.reject(key)
|
|
887
|
+
}
|
|
888
|
+
kind = diffRefTargets({ keyTable, rowsOld: rowsPlan.rowsOld, rowsNew: rowsPlan.rowsNew, opt: { cascadeRename } })
|
|
889
|
+
|
|
890
|
+
//外部 id 不穩定之偵測(主控定案: 不加開關; 同一 from 同一批內出現「同名異 id」=疑似 id 重編, id 消失視為刪除會每次清理引用而可能抹掉後台指派): 記 warn log(不記個資值, 只記筆數)
|
|
891
|
+
//並登錄供 getWebInfor.syncWarns 於頁面提示; 之後同一(表, from)之同步已無此情形即清除提示. 只適用有 name 之 grups/pemis(users 之 name 可重複, targets 之 id 即名稱)
|
|
892
|
+
if (keyTable === 'grups' || keyTable === 'pemis') {
|
|
893
|
+
let nRenum = countIdRenumber(rowsPlan.rowsOld.filter((o) => o !== null && typeof o === 'object' && o.from === from), rows)
|
|
894
|
+
setSyncWarn(keyTable, from, nRenum)
|
|
895
|
+
if (nRenum > 0) {
|
|
896
|
+
srLog.warn({ event: 'syncAndReplaceTabs-idRenumber', keyTable, from, n: nRenum })
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
//doWrite: delAll from, insert; ORM/I/O 失敗之原文只入 srLog, 外部應用收 saveWriteFail(規則帳本 R-ORM-02); 引用端先寫、失敗補償還原(cascadeWrite.mjs)
|
|
902
|
+
let doWrite = async (plan, holdersOld) => {
|
|
903
|
+
let r = null
|
|
904
|
+
try {
|
|
905
|
+
r = await writeWithCascade({
|
|
906
|
+
plan,
|
|
907
|
+
holdersOld,
|
|
908
|
+
woItems,
|
|
909
|
+
procOrm,
|
|
910
|
+
userId,
|
|
911
|
+
srLog,
|
|
912
|
+
writeMain: async () => {
|
|
913
|
+
await woItems[keyTable].delAll({ from })
|
|
914
|
+
return await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
|
|
915
|
+
},
|
|
916
|
+
})
|
|
917
|
+
}
|
|
918
|
+
catch (err) {
|
|
919
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
920
|
+
let key = toOutKey(err)
|
|
921
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from })
|
|
922
|
+
return Promise.reject(key)
|
|
923
|
+
}
|
|
924
|
+
// console.log('r', r)
|
|
925
|
+
return r
|
|
750
926
|
}
|
|
751
|
-
// console.log('r', r)
|
|
752
927
|
|
|
753
|
-
|
|
928
|
+
//有改名或刪除: 再取引用端表之鎖, 讀取引用端、規劃、預檢後寫入
|
|
929
|
+
if (kind !== null && (kind.renames.length > 0 || kind.delNames.length > 0)) {
|
|
930
|
+
let holder = REF_OF[keyTable].holder
|
|
931
|
+
return await withTabLocks(kmx, [holder], async () => {
|
|
932
|
+
let holdersOld = null
|
|
933
|
+
try {
|
|
934
|
+
holdersOld = await woItems[holder].select()
|
|
935
|
+
}
|
|
936
|
+
catch (err) {
|
|
937
|
+
let raw = (typeof err === 'string') ? err : ((err && err.message) || String(err))
|
|
938
|
+
let key = toOutKey(err, 'anUnexpectedErrorOccurred')
|
|
939
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, raw, keyTable, from, step: 'select' })
|
|
940
|
+
return Promise.reject(key)
|
|
941
|
+
}
|
|
942
|
+
let plan = planRefChanges({ keyTable, rowsOld: rowsPlan.rowsOld, rowsNew: rowsPlan.rowsNew, holdersOld, opt: { cascadeRename } })
|
|
943
|
+
let errPlan = plan.err !== null ? plan.err : checkCascadePlan(plan)
|
|
944
|
+
if (errPlan !== null) {
|
|
945
|
+
let key = (errPlan === 'htmlMarkupNotAllowed') ? 'htmlMarkupNotAllowed' : 'invalidRows'
|
|
946
|
+
srLog.error({ event: 'syncAndReplaceTabs-error', msg: key, reason: errPlan, keyTable, from })
|
|
947
|
+
return Promise.reject(key)
|
|
948
|
+
}
|
|
949
|
+
return await doWrite(plan, holdersOld)
|
|
950
|
+
}, [keyTable])
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return await doWrite(null, [])
|
|
754
954
|
|
|
755
955
|
})
|
|
756
956
|
}
|
|
@@ -759,9 +959,9 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
759
959
|
//getGenUserByKV
|
|
760
960
|
let getGenUserByKV = async(keyUser, valueUser) => {
|
|
761
961
|
|
|
762
|
-
//userFind
|
|
763
|
-
let
|
|
764
|
-
userFind =
|
|
962
|
+
//userFind, 與 getTokenUser 同一判準(pickUniqueUser): 非恰 1 筆不取首筆(目前僅以主鍵 id 呼叫, 恆 ≤ 1 筆)
|
|
963
|
+
let userRs = await woItems.users.select({ [keyUser]: valueUser, isActive: 'y' })
|
|
964
|
+
let { user: userFind } = pickUniqueUser(userRs)
|
|
765
965
|
|
|
766
966
|
//check
|
|
767
967
|
if (!iseobj(userFind)) {
|
|
@@ -806,8 +1006,17 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
806
1006
|
let targets = await woItems.targets.select()
|
|
807
1007
|
// console.log('targets', targets)
|
|
808
1008
|
|
|
809
|
-
//getUserRules
|
|
810
|
-
let kur =
|
|
1009
|
+
//getUserRules, 評估時之任何例外一律轉為固定語系鍵 cannotGetUserRules: 不得把引擎原文(如 TypeError 的 message)經 getPerm/getPermUserInfor 回給外部應用(R-KEY-PROP-01, 錯誤一律回 key)
|
|
1010
|
+
let kur = null
|
|
1011
|
+
try {
|
|
1012
|
+
kur = getUserRules(userFind, grups, pemis, targets)
|
|
1013
|
+
}
|
|
1014
|
+
catch (err) {
|
|
1015
|
+
console.log('userId', userId)
|
|
1016
|
+
console.log('getUserRules', err)
|
|
1017
|
+
srLog.error({ event: 'getGenUserAndRulesByUserId-error', msg: 'cannotGetUserRules', userId })
|
|
1018
|
+
return Promise.reject(`cannotGetUserRules`)
|
|
1019
|
+
}
|
|
811
1020
|
// console.log('getUserRules', kur)
|
|
812
1021
|
|
|
813
1022
|
//check
|
|
@@ -1256,12 +1465,16 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
|
|
|
1256
1465
|
|
|
1257
1466
|
getWebInfor,
|
|
1258
1467
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1468
|
+
//updateXxx 第 3 參 opt 為用戶端給之 { baseline }(草稿載入基準, F-26, R-BASELINE-01), 其餘鍵一律不採納(pickKeysOnly 等為伺服端設定); 未給(舊客戶端)見 procCore 之處置
|
|
1469
|
+
updateTargets: (userId, rows, opt) => pc.updateTargets(userId, rows, opt),
|
|
1470
|
+
updatePemis: (userId, rows, opt) => pc.updatePemis(userId, rows, opt),
|
|
1471
|
+
updateGrups: (userId, rows, opt) => pc.updateGrups(userId, rows, opt),
|
|
1262
1472
|
//updateUsers, modeEditUsers='for:grups' 時 API 與 UI 一致只採納既有使用者之 cgrups 變更, 身分欄/新增/刪除/順序以資料庫為準
|
|
1263
|
-
updateUsers: (userId, rows) => {
|
|
1264
|
-
let opt = (
|
|
1473
|
+
updateUsers: (userId, rows, optClient) => {
|
|
1474
|
+
let opt = (optClient !== null && typeof optClient === 'object' && optClient.baseline !== undefined && optClient.baseline !== null) ? { baseline: optClient.baseline } : {}
|
|
1475
|
+
if (modeEditUsers === 'for:grups') {
|
|
1476
|
+
opt.pickKeysOnly = ['cgrups']
|
|
1477
|
+
}
|
|
1265
1478
|
return pc.updateUsers(userId, rows, opt)
|
|
1266
1479
|
},
|
|
1267
1480
|
|
|
@@ -1324,7 +1537,7 @@ export default WWebPerm
|
|
|
1324
1537
|
<br class="clear">
|
|
1325
1538
|
|
|
1326
1539
|
<footer>
|
|
1327
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
1540
|
+
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.
|
|
1328
1541
|
</footer>
|
|
1329
1542
|
|
|
1330
1543
|
<script>prettyPrint();</script>
|