iobroker.javascript 10.2.5 → 10.4.0
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 +26 -23
- package/admin/assets/AiChatPanel-BjRIKaXG.js +27 -0
- package/admin/assets/{AiChatService-BOL3J2Jd.js → AiChatService-DP1EmVk2.js} +3 -3
- package/admin/assets/AiDiffView-DeynDw7L.js +1 -0
- package/admin/assets/{AiInlineProvider-BIlcrTkS.js → AiInlineProvider-jkEIJJJ-.js} +1 -1
- package/admin/assets/BlocklyEditor-DhwThchX.js +301 -0
- package/admin/assets/Compile-B0uHjrBE.css +1 -0
- package/admin/assets/Compile-e8N5zI-J.js +165 -0
- package/admin/assets/Debugger-BDQUKM1K.js +15 -0
- package/admin/assets/Diff--plofAtP.js +1 -0
- package/admin/assets/{Documentation-Bc0RmoEF.js → Documentation-D3E0U4AK.js} +4 -4
- package/admin/assets/ExpandMore-Dio4GeHC.js +1 -0
- package/admin/assets/FbEditor-B8S-mCvc.css +1 -0
- package/admin/assets/FbEditor-BjLPNzpK.js +15 -0
- package/admin/assets/History-Ewi5YEEn.js +1 -0
- package/admin/assets/Import-6Mm2vrM2.js +1 -0
- package/admin/assets/MonacoDiff-BYvG7IqB.js +1 -0
- package/admin/assets/NotificationsActive-CuNfWVJM.js +1 -0
- package/admin/assets/RulesEditor--YX6ETsU.js +1 -0
- package/admin/assets/{ScriptEditor-CEHpEFp7.js → ScriptEditor-BadPC8Mf.js} +2 -2
- package/admin/assets/ScriptEditorVanillaMonaco-BnAmy59d.js +3 -0
- package/admin/assets/Send-Pz1eveU5.js +1 -0
- package/admin/assets/_virtual_mf-localSharedImportMap___mfe_internal__iobroker_javascript__mf_owner__1-Ceh8EDcF.js +1 -0
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__234968779812303__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BRdHvuuG.js +1217 -0
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__234968779812303__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-DLWT3PGo.js +9 -0
- package/admin/assets/aiPromptBuilder-Co6AqZwx.js +1 -0
- package/admin/assets/{blockly-fcfyKYzp.js → blockly-B0t_IRJQ.js} +1 -1
- package/admin/assets/blocklyTheme-iZoge1S9.js +1 -0
- package/admin/assets/blocks_action-Ctn5A1AU.js +1 -0
- package/admin/assets/{blocks_convert-BP2RjhQH.js → blocks_convert-BI0lf0sf.js} +1 -1
- package/admin/assets/blocks_logic-o49klUX_.js +1 -0
- package/admin/assets/blocks_number-DZtyWFyR.js +1 -0
- package/admin/assets/blocks_object-BuzGCxVr.js +1 -0
- package/admin/assets/blocks_procedures-jn9sIYmR.js +3 -0
- package/admin/assets/blocks_sendto-Coui8w1H.js +5 -0
- package/admin/assets/blocks_switch-CeRZizTM.js +2 -0
- package/admin/assets/blocks_system-CaBPIC40.js +1 -0
- package/admin/assets/blocks_text-DeDtPYWf.js +1 -0
- package/admin/assets/blocks_time-CTXQH3Lm.js +1 -0
- package/admin/assets/blocks_timeout-D8IOKOb-.js +1 -0
- package/admin/assets/{blocks_trigger-CnmXLCUE.js → blocks_trigger-zuuXJbhb.js} +2 -2
- package/admin/assets/{cronHoverProvider-CcIFAhVy.js → cronHoverProvider-Curcjz4m.js} +1 -1
- package/admin/assets/dist-CzitsFi7.js +1 -0
- package/admin/assets/field_cron-RgLY53_S.js +1 -0
- package/admin/assets/field_oid-rBhotXJe.js +1 -0
- package/admin/assets/field_script-CU7g3-oB.js +1 -0
- package/admin/assets/helpers-BZgjFQNf.js +1 -0
- package/admin/assets/helpers-BwllSRzJ.js +1 -0
- package/admin/assets/hostInit-CrHjJGJJ.js +1 -0
- package/admin/assets/index-BHhhebRG.css +1 -0
- package/admin/assets/index-Bmmscq2G.js +157 -0
- package/admin/assets/{inlineChatWidget-N8xIkvxX.js → inlineChatWidget-ChchJD9a.js} +2 -2
- package/admin/assets/{inlineDiffController-BuqYsKKq.js → inlineDiffController-ly6qX4jk.js} +2 -2
- package/admin/assets/loadBlocks-BcFtthDb.js +117 -0
- package/admin/assets/loadMonaco-BVOEahNw.js +1 -0
- package/admin/assets/{mf-entry-bootstrap-0-a5a39621.js → mf-entry-bootstrap-0-6db6c4fc.js} +3 -3
- package/admin/assets/pendingShares-mBGPbRwZ.js +2 -0
- package/admin/assets/reactour.esm-Dk3aGxKn.js +137 -0
- package/admin/assets/{stateHoverProvider-BYAbe1Ay.js → stateHoverProvider-wAsP-THx.js} +1 -1
- package/admin/assets/tileFbd-CWAy7R7m.svg +85 -0
- package/admin/assets/utils-DUKzLJUz.js +4 -0
- package/admin/assets/vite-preload-helper-DjNwdemH.js +1 -0
- package/admin/custom/assets/Components-fDcxCyN4.js +1 -0
- package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1-BraOrMSu.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-DbKT6Hsb.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-CVvbIPAy.js +1 -0
- package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DVb0ZZ1Q.js → _virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BhZYie9-.js} +89 -89
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare___mf_0_mui_mf_1_x_mf_2_date_mf_2_pickers__loadShare__.js-CV50dgj4.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare__date_mf_2_fns__loadShare__.js-Djg9OMcQ.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare__leaflet__loadShare__.js-D9EyCd42.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__loadShare__shared-Hb6V6BnJ.js +2 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild___mf_0_iobroker_mf_1_json_mf_2_config__prebuild__-BrY-nF2a.js +122 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild__leaflet__prebuild__-CHjWtrkp.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild__react_mf_2_dom_mf_1_client__prebuild__-DOkC-g0X.js +9 -0
- package/admin/custom/assets/dist-CzitsFi7.js +1 -0
- package/admin/custom/assets/helpers-BZgjFQNf.js +1 -0
- package/admin/custom/assets/hostInit-akwaQLq8.js +1 -0
- package/admin/custom/assets/index-BkItz-nL.js +1 -0
- package/admin/custom/assets/{lib-BmDlTzq2.js → lib-CADOx2YN.js} +1 -1
- package/admin/custom/assets/{mf-entry-bootstrap-0-ad1cd7d0.js → mf-entry-bootstrap-0-3c4565e0.js} +3 -3
- package/admin/custom/assets/pendingShares-TCMDgBLn.js +2 -0
- package/admin/custom/assets/utils-DUKzLJUz.js +4 -0
- package/admin/custom/assets/vite-preload-helper-DjNwdemH.js +1 -0
- package/admin/custom/customComponents.js +2 -2
- package/admin/custom/i18n/de.json +19 -1
- package/admin/custom/i18n/en.json +19 -1
- package/admin/custom/i18n/es.json +19 -1
- package/admin/custom/i18n/fr.json +19 -1
- package/admin/custom/i18n/it.json +19 -1
- package/admin/custom/i18n/nl.json +19 -1
- package/admin/custom/i18n/pl.json +19 -1
- package/admin/custom/i18n/pt.json +19 -1
- package/admin/custom/i18n/ru.json +19 -1
- package/admin/custom/i18n/uk.json +19 -1
- package/admin/custom/i18n/zh-cn.json +19 -1
- package/admin/i18n/de.json +13 -1
- package/admin/i18n/en.json +13 -1
- package/admin/i18n/es.json +13 -1
- package/admin/i18n/fr.json +13 -1
- package/admin/i18n/it.json +13 -1
- package/admin/i18n/nl.json +13 -1
- package/admin/i18n/pl.json +13 -1
- package/admin/i18n/pt.json +13 -1
- package/admin/i18n/ru.json +13 -1
- package/admin/i18n/uk.json +13 -1
- package/admin/i18n/zh-cn.json +13 -1
- package/admin/jsonConfig.json +274 -86
- package/admin/mf-manifest.json +1 -1
- package/admin/mf-stats.json +1 -1
- package/admin/remoteEntry.js +2 -2
- package/admin/tab.html +3 -7
- package/build/lib/aiProviderResolver.js +145 -25
- package/build/lib/aiProviderResolver.js.map +1 -1
- package/build/lib/anthropicAdapter.js +37 -0
- package/build/lib/anthropicAdapter.js.map +1 -1
- package/build/lib/fb/analyze.js +288 -0
- package/build/lib/fb/analyze.js.map +1 -0
- package/build/lib/fb/blocks.js +605 -0
- package/build/lib/fb/blocks.js.map +1 -0
- package/build/lib/fb/generate.js +326 -0
- package/build/lib/fb/generate.js.map +1 -0
- package/build/lib/fb/graph.js +240 -0
- package/build/lib/fb/graph.js.map +1 -0
- package/build/lib/fb/index.js +57 -0
- package/build/lib/fb/index.js.map +1 -0
- package/build/lib/fb/js.js +33 -0
- package/build/lib/fb/js.js.map +1 -0
- package/build/lib/fb/layout.js +205 -0
- package/build/lib/fb/layout.js.map +1 -0
- package/build/lib/fb/library.js +657 -0
- package/build/lib/fb/library.js.map +1 -0
- package/build/lib/fb/runtime.js +793 -0
- package/build/lib/fb/runtime.js.map +1 -0
- package/build/lib/fb/time.js +76 -0
- package/build/lib/fb/time.js.map +1 -0
- package/build/lib/fb/types.js +19 -0
- package/build/lib/fb/types.js.map +1 -0
- package/build/lib/fb/user.js +78 -0
- package/build/lib/fb/user.js.map +1 -0
- package/build/lib/inspect.js +66 -10
- package/build/lib/inspect.js.map +1 -1
- package/build/lib/javascript.d.ts +1 -1
- package/build/lib/mirror.js +28 -23
- package/build/lib/mirror.js.map +1 -1
- package/build/lib/sandbox.js +44 -0
- package/build/lib/sandbox.js.map +1 -1
- package/build/lib/scriptHistory.js +245 -0
- package/build/lib/scriptHistory.js.map +1 -0
- package/build/main.js +641 -32
- package/build/main.js.map +1 -1
- package/build/types.d.ts +20 -1
- package/docs/de/fbd.md +274 -0
- package/docs/en/fbd.md +260 -0
- package/io-package.json +55 -29
- package/package.json +7 -7
- package/admin/assets/AiChatPanel-oBeXbUFv.js +0 -27
- package/admin/assets/AiDiffView-C-KKjnJD.js +0 -1
- package/admin/assets/Debugger-DwHHsh9O.js +0 -15
- package/admin/assets/Error-DZ4vCykD.js +0 -1
- package/admin/assets/Import-Cu02ceLW.js +0 -1
- package/admin/assets/RulesEditor-QCbi1lV_.js +0 -1
- package/admin/assets/ScriptEditorVanillaMonaco-ChQmnk0p.js +0 -3
- package/admin/assets/_virtual_mf-localSharedImportMap___mfe_internal__iobroker_javascript__mf_owner__1-CM95r358.js +0 -1
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__180366418666083__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BEpNZh4U.js +0 -1217
- package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__180366418666083__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-FeM77flT.js +0 -9
- package/admin/assets/aiPromptBuilder-DKler0UK.js +0 -1
- package/admin/assets/blocks_action-DywZVfRY.js +0 -1
- package/admin/assets/blocks_logic-CdTl5YZw.js +0 -1
- package/admin/assets/blocks_number-DXFd9Rva.js +0 -1
- package/admin/assets/blocks_object-by1uBDHi.js +0 -1
- package/admin/assets/blocks_procedures-BcJ7N70D.js +0 -3
- package/admin/assets/blocks_sendto-qIKzOAcV.js +0 -5
- package/admin/assets/blocks_switch-dLF6hSJl.js +0 -2
- package/admin/assets/blocks_system-DmX-SBy8.js +0 -1
- package/admin/assets/blocks_text-BI4zNU3o.js +0 -1
- package/admin/assets/blocks_time-BgYA4Y_4.js +0 -1
- package/admin/assets/blocks_timeout-DUzgMyAu.js +0 -1
- package/admin/assets/dist-D4uJqRBO.js +0 -4
- package/admin/assets/field_cron-Ba46q8Qc.js +0 -1
- package/admin/assets/field_oid-lWCjOzuq.js +0 -1
- package/admin/assets/field_script-BCWZHhnW.js +0 -1
- package/admin/assets/helpers-CnEjYAup.js +0 -1
- package/admin/assets/hostInit-CVybS7BB.js +0 -1
- package/admin/assets/index-B3s0zmWC.js +0 -871
- package/admin/assets/index-CXaeqPRy.css +0 -1
- package/admin/assets/pendingShares-DOAkyFMS.js +0 -1
- package/admin/assets/vite-preload-helper-CaC3yEL3.js +0 -1
- package/admin/custom/assets/Components-FkGDIlId.js +0 -1
- package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1-Cs88NUCd.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-P-8ffWXw.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-ZVuRAEo5.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-Dna3WF04.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare___mf_0_mui_mf_1_x_mf_2_date_mf_2_pickers__loadShare__.js-DcjdCGAx.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare__date_mf_2_fns__loadShare__.js-CnhNjamd.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare__leaflet__loadShare__.js-Dz7BFJqH.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-B0olko2J.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild___mf_0_iobroker_mf_1_json_mf_2_config__prebuild__-CzN4reh_.js +0 -122
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild__leaflet__prebuild__-xPcBaiTQ.js +0 -1
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild__react_mf_2_dom_mf_1_client__prebuild__-DuFBE5fh.js +0 -9
- package/admin/custom/assets/dist-D4uJqRBO.js +0 -4
- package/admin/custom/assets/hostInit-6oh4UY2k.js +0 -1
- package/admin/custom/assets/index-BrlyEy6J.js +0 -1
- package/admin/custom/assets/pendingShares-kV2kwejX.js +0 -1
- package/admin/custom/assets/vite-preload-helper-CaC3yEL3.js +0 -1
- /package/admin/custom/assets/{_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__37453319418803__prebuild__react_mf_1_jsx_mf_2_runtime__prebuild__-CXpyx8Vy.js → _virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__198282747311932__prebuild__react_mf_1_jsx_mf_2_runtime__prebuild__-CXpyx8Vy.js} +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Storage layout and bookkeeping for the script history.
|
|
4
|
+
*
|
|
5
|
+
* Every saved source lands in the adapter's file database as one file per version, next to a small
|
|
6
|
+
* index that carries nothing but the metadata. Listing a history therefore reads one small file,
|
|
7
|
+
* and writing a version does not rewrite the sources already there - which matters for Blockly and
|
|
8
|
+
* FBD scripts, whose source is the generated code *plus* the model in one string.
|
|
9
|
+
*
|
|
10
|
+
* The functions here are pure so they can be tested without an adapter; `main.ts` does the file IO.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.MAX_HISTORY_VERSIONS = exports.DEFAULT_HISTORY_VERSIONS = exports.INDEX_FILE = exports.HISTORY_ROOT = void 0;
|
|
14
|
+
exports.versionFileName = versionFileName;
|
|
15
|
+
exports.historyFolder = historyFolder;
|
|
16
|
+
exports.indexPath = indexPath;
|
|
17
|
+
exports.versionPath = versionPath;
|
|
18
|
+
exports.resolveHistoryVersions = resolveHistoryVersions;
|
|
19
|
+
exports.parseIndex = parseIndex;
|
|
20
|
+
exports.addVersion = addVersion;
|
|
21
|
+
exports.isWorthStoring = isWorthStoring;
|
|
22
|
+
exports.describeSource = describeSource;
|
|
23
|
+
exports.fileToString = fileToString;
|
|
24
|
+
exports.decodeFolderName = decodeFolderName;
|
|
25
|
+
exports.sumIndex = sumIndex;
|
|
26
|
+
exports.formatBytes = formatBytes;
|
|
27
|
+
exports.trimIndex = trimIndex;
|
|
28
|
+
/** Folder inside the instance's file area that holds every script's history */
|
|
29
|
+
exports.HISTORY_ROOT = 'history';
|
|
30
|
+
/** Name of the metadata file inside a script's history folder */
|
|
31
|
+
exports.INDEX_FILE = 'index.json';
|
|
32
|
+
/** Versions kept per script when the setting says nothing */
|
|
33
|
+
exports.DEFAULT_HISTORY_VERSIONS = 30;
|
|
34
|
+
/** Nobody is served by more than this, whatever the setting says */
|
|
35
|
+
exports.MAX_HISTORY_VERSIONS = 200;
|
|
36
|
+
/**
|
|
37
|
+
* The file name of one version. The timestamp is the identity of a version, so it is also its name.
|
|
38
|
+
*
|
|
39
|
+
* @param ts timestamp of the version
|
|
40
|
+
*/
|
|
41
|
+
function versionFileName(ts) {
|
|
42
|
+
return `${ts}.txt`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Where a script's history lives.
|
|
46
|
+
*
|
|
47
|
+
* The script id is encoded: it is a user-chosen name and may contain characters that have a meaning
|
|
48
|
+
* in a path.
|
|
49
|
+
*
|
|
50
|
+
* @param scriptId full id of the script, e.g. `script.js.Folder.Name`
|
|
51
|
+
*/
|
|
52
|
+
function historyFolder(scriptId) {
|
|
53
|
+
return `${exports.HISTORY_ROOT}/${encodeURIComponent(scriptId)}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Path of a script's index file.
|
|
57
|
+
*
|
|
58
|
+
* @param scriptId full id of the script
|
|
59
|
+
*/
|
|
60
|
+
function indexPath(scriptId) {
|
|
61
|
+
return `${historyFolder(scriptId)}/${exports.INDEX_FILE}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Path of one stored version.
|
|
65
|
+
*
|
|
66
|
+
* @param scriptId full id of the script
|
|
67
|
+
* @param ts timestamp of the version
|
|
68
|
+
*/
|
|
69
|
+
function versionPath(scriptId, ts) {
|
|
70
|
+
return `${historyFolder(scriptId)}/${versionFileName(ts)}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* How many versions to keep, from the adapter setting.
|
|
74
|
+
*
|
|
75
|
+
* Zero switches the history off, which is why it is not simply clamped into the valid range.
|
|
76
|
+
*
|
|
77
|
+
* @param configured the value from the adapter settings
|
|
78
|
+
*/
|
|
79
|
+
function resolveHistoryVersions(configured) {
|
|
80
|
+
const requested = parseInt(configured, 10);
|
|
81
|
+
if (isNaN(requested)) {
|
|
82
|
+
return exports.DEFAULT_HISTORY_VERSIONS;
|
|
83
|
+
}
|
|
84
|
+
if (requested <= 0) {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
return Math.min(requested, exports.MAX_HISTORY_VERSIONS);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Reads an index file. Anything unreadable counts as "no history yet" rather than as an error:
|
|
91
|
+
* a broken index must not stop a script from being saved.
|
|
92
|
+
*
|
|
93
|
+
* @param raw contents of the index file
|
|
94
|
+
*/
|
|
95
|
+
function parseIndex(raw) {
|
|
96
|
+
if (!raw) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(raw);
|
|
101
|
+
if (!Array.isArray(parsed)) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
return parsed
|
|
105
|
+
.filter(entry => entry && typeof entry.ts === 'number')
|
|
106
|
+
.sort((a, b) => b.ts - a.ts);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Add a version to an index and work out which stored files are now surplus.
|
|
114
|
+
*
|
|
115
|
+
* The newest entry stays newest even if two saves land in the same millisecond, and the very first
|
|
116
|
+
* version is never dropped - it is the one worth having when everything else has scrolled away.
|
|
117
|
+
*
|
|
118
|
+
* @param index the index as it is on disk, newest first
|
|
119
|
+
* @param entry the version that was just written
|
|
120
|
+
* @param keep how many versions to keep
|
|
121
|
+
*/
|
|
122
|
+
function addVersion(index, entry, keep) {
|
|
123
|
+
const next = [entry, ...index.filter(v => v.ts !== entry.ts)].sort((a, b) => b.ts - a.ts);
|
|
124
|
+
if (next.length <= keep) {
|
|
125
|
+
return { index: next, obsolete: [] };
|
|
126
|
+
}
|
|
127
|
+
// The oldest entry is the original state of the script and is kept on top of the quota
|
|
128
|
+
const oldest = next[next.length - 1];
|
|
129
|
+
const kept = next.slice(0, keep - 1);
|
|
130
|
+
const obsolete = next.slice(keep - 1, next.length - 1).map(v => v.ts);
|
|
131
|
+
return { index: [...kept, oldest], obsolete };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether this change is worth a new version.
|
|
135
|
+
*
|
|
136
|
+
* A script object is written for all sorts of reasons - enabling it, renaming it, the adapter
|
|
137
|
+
* storing its compiled form - and none of those changed the source. Without this the history would
|
|
138
|
+
* be full of entries that differ in nothing.
|
|
139
|
+
*
|
|
140
|
+
* @param previous the source before the change, if any
|
|
141
|
+
* @param next the source after the change
|
|
142
|
+
*/
|
|
143
|
+
function isWorthStoring(previous, next) {
|
|
144
|
+
if (typeof next !== 'string' || !next) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return previous !== next;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Describe a source for the index.
|
|
151
|
+
*
|
|
152
|
+
* @param source the source to describe
|
|
153
|
+
* @param isProtected whether it is stored encrypted
|
|
154
|
+
*/
|
|
155
|
+
function describeSource(source, isProtected) {
|
|
156
|
+
return {
|
|
157
|
+
size: source.length,
|
|
158
|
+
lines: source.split('\n').length,
|
|
159
|
+
...(isProtected ? { protected: true } : {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The content of a file read through the adapter, as a string.
|
|
164
|
+
*
|
|
165
|
+
* `readFile` answers with the content for some back ends and with `{ file, mimeType }` for others,
|
|
166
|
+
* and the content itself is a string or a buffer. Everything else counts as "nothing there".
|
|
167
|
+
*
|
|
168
|
+
* @param raw whatever `readFileAsync` resolved with
|
|
169
|
+
*/
|
|
170
|
+
function fileToString(raw) {
|
|
171
|
+
if (raw === null || raw === undefined) {
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
const file = typeof raw === 'object' && 'file' in raw ? raw.file : raw;
|
|
175
|
+
if (typeof file === 'string') {
|
|
176
|
+
return file;
|
|
177
|
+
}
|
|
178
|
+
if (file instanceof Uint8Array) {
|
|
179
|
+
return Buffer.from(file).toString('utf8');
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The script a history folder belongs to.
|
|
185
|
+
*
|
|
186
|
+
* @param folder the folder name as `readDir` reports it
|
|
187
|
+
*/
|
|
188
|
+
function decodeFolderName(folder) {
|
|
189
|
+
try {
|
|
190
|
+
return decodeURIComponent(folder);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// A folder nobody of ours wrote - report it under its own name rather than throwing
|
|
194
|
+
return folder;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* How much one history occupies, from its index alone - the stored sources are never read for this.
|
|
199
|
+
*
|
|
200
|
+
* @param index the index of a script
|
|
201
|
+
*/
|
|
202
|
+
function sumIndex(index) {
|
|
203
|
+
return {
|
|
204
|
+
versions: index.length,
|
|
205
|
+
bytes: index.reduce((sum, entry) => sum + (entry.size || 0), 0),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* A size a human can read at a glance.
|
|
210
|
+
*
|
|
211
|
+
* @param bytes the number of bytes
|
|
212
|
+
*/
|
|
213
|
+
function formatBytes(bytes) {
|
|
214
|
+
if (bytes < 1024) {
|
|
215
|
+
return `${bytes} B`;
|
|
216
|
+
}
|
|
217
|
+
if (bytes < 1024 * 1024) {
|
|
218
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
219
|
+
}
|
|
220
|
+
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Cut an index down to the current limit, for instance after the setting was made smaller.
|
|
224
|
+
*
|
|
225
|
+
* Follows the same rule as `addVersion`: the newest ones are kept and the very first version
|
|
226
|
+
* survives on top of the quota.
|
|
227
|
+
*
|
|
228
|
+
* @param index the index as it is on disk, newest first
|
|
229
|
+
* @param keep how many versions to keep; `0` drops everything
|
|
230
|
+
*/
|
|
231
|
+
function trimIndex(index, keep) {
|
|
232
|
+
const sorted = [...index].sort((a, b) => b.ts - a.ts);
|
|
233
|
+
if (!keep) {
|
|
234
|
+
return { index: [], obsolete: sorted.map(v => v.ts) };
|
|
235
|
+
}
|
|
236
|
+
if (sorted.length <= keep) {
|
|
237
|
+
return { index: sorted, obsolete: [] };
|
|
238
|
+
}
|
|
239
|
+
const oldest = sorted[sorted.length - 1];
|
|
240
|
+
return {
|
|
241
|
+
index: [...sorted.slice(0, keep - 1), oldest],
|
|
242
|
+
obsolete: sorted.slice(keep - 1, sorted.length - 1).map(v => v.ts),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=scriptHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scriptHistory.js","sourceRoot":"","sources":["../../src/lib/scriptHistory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage layout and bookkeeping for the script history.\n *\n * Every saved source lands in the adapter's file database as one file per version, next to a small\n * index that carries nothing but the metadata. Listing a history therefore reads one small file,\n * and writing a version does not rewrite the sources already there - which matters for Blockly and\n * FBD scripts, whose source is the generated code *plus* the model in one string.\n *\n * The functions here are pure so they can be tested without an adapter; `main.ts` does the file IO.\n */\n\n/** Folder inside the instance's file area that holds every script's history */\nexport const HISTORY_ROOT = 'history';\n/** Name of the metadata file inside a script's history folder */\nexport const INDEX_FILE = 'index.json';\n/** Versions kept per script when the setting says nothing */\nexport const DEFAULT_HISTORY_VERSIONS = 30;\n/** Nobody is served by more than this, whatever the setting says */\nexport const MAX_HISTORY_VERSIONS = 200;\n\n/** What is known about one stored version, without its source */\nexport interface ScriptVersion {\n /** When it was saved, ms since the epoch - also the name of its file */\n ts: number;\n /** Which adapter wrote it, e.g. `system.adapter.admin.0` */\n from?: string;\n /** Which user wrote it, e.g. `system.user.admin` */\n user?: string;\n /** Length of the source in characters */\n size: number;\n /** Number of lines of the source */\n lines: number;\n /** The source was stored encrypted, as it is in the object (`native.protected`) */\n protected?: boolean;\n}\n\n/**\n * The file name of one version. The timestamp is the identity of a version, so it is also its name.\n *\n * @param ts timestamp of the version\n */\nexport function versionFileName(ts: number): string {\n return `${ts}.txt`;\n}\n\n/**\n * Where a script's history lives.\n *\n * The script id is encoded: it is a user-chosen name and may contain characters that have a meaning\n * in a path.\n *\n * @param scriptId full id of the script, e.g. `script.js.Folder.Name`\n */\nexport function historyFolder(scriptId: string): string {\n return `${HISTORY_ROOT}/${encodeURIComponent(scriptId)}`;\n}\n\n/**\n * Path of a script's index file.\n *\n * @param scriptId full id of the script\n */\nexport function indexPath(scriptId: string): string {\n return `${historyFolder(scriptId)}/${INDEX_FILE}`;\n}\n\n/**\n * Path of one stored version.\n *\n * @param scriptId full id of the script\n * @param ts timestamp of the version\n */\nexport function versionPath(scriptId: string, ts: number): string {\n return `${historyFolder(scriptId)}/${versionFileName(ts)}`;\n}\n\n/**\n * How many versions to keep, from the adapter setting.\n *\n * Zero switches the history off, which is why it is not simply clamped into the valid range.\n *\n * @param configured the value from the adapter settings\n */\nexport function resolveHistoryVersions(configured?: unknown): number {\n const requested = parseInt(configured as string, 10);\n if (isNaN(requested)) {\n return DEFAULT_HISTORY_VERSIONS;\n }\n if (requested <= 0) {\n return 0;\n }\n return Math.min(requested, MAX_HISTORY_VERSIONS);\n}\n\n/**\n * Reads an index file. Anything unreadable counts as \"no history yet\" rather than as an error:\n * a broken index must not stop a script from being saved.\n *\n * @param raw contents of the index file\n */\nexport function parseIndex(raw: string | undefined | null): ScriptVersion[] {\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return (parsed as ScriptVersion[])\n .filter(entry => entry && typeof entry.ts === 'number')\n .sort((a, b) => b.ts - a.ts);\n } catch {\n return [];\n }\n}\n\n/**\n * Add a version to an index and work out which stored files are now surplus.\n *\n * The newest entry stays newest even if two saves land in the same millisecond, and the very first\n * version is never dropped - it is the one worth having when everything else has scrolled away.\n *\n * @param index the index as it is on disk, newest first\n * @param entry the version that was just written\n * @param keep how many versions to keep\n */\nexport function addVersion(\n index: ScriptVersion[],\n entry: ScriptVersion,\n keep: number,\n): { index: ScriptVersion[]; obsolete: number[] } {\n const next = [entry, ...index.filter(v => v.ts !== entry.ts)].sort((a, b) => b.ts - a.ts);\n if (next.length <= keep) {\n return { index: next, obsolete: [] };\n }\n // The oldest entry is the original state of the script and is kept on top of the quota\n const oldest = next[next.length - 1];\n const kept = next.slice(0, keep - 1);\n const obsolete = next.slice(keep - 1, next.length - 1).map(v => v.ts);\n return { index: [...kept, oldest], obsolete };\n}\n\n/**\n * Whether this change is worth a new version.\n *\n * A script object is written for all sorts of reasons - enabling it, renaming it, the adapter\n * storing its compiled form - and none of those changed the source. Without this the history would\n * be full of entries that differ in nothing.\n *\n * @param previous the source before the change, if any\n * @param next the source after the change\n */\nexport function isWorthStoring(previous: string | undefined | null, next: string | undefined | null): boolean {\n if (typeof next !== 'string' || !next) {\n return false;\n }\n return previous !== next;\n}\n\n/**\n * Describe a source for the index.\n *\n * @param source the source to describe\n * @param isProtected whether it is stored encrypted\n */\nexport function describeSource(\n source: string,\n isProtected: boolean,\n): Pick<ScriptVersion, 'size' | 'lines'> & {\n protected?: boolean;\n} {\n return {\n size: source.length,\n lines: source.split('\\n').length,\n ...(isProtected ? { protected: true } : {}),\n };\n}\n\n/**\n * The content of a file read through the adapter, as a string.\n *\n * `readFile` answers with the content for some back ends and with `{ file, mimeType }` for others,\n * and the content itself is a string or a buffer. Everything else counts as \"nothing there\".\n *\n * @param raw whatever `readFileAsync` resolved with\n */\nexport function fileToString(raw: unknown): string {\n if (raw === null || raw === undefined) {\n return '';\n }\n const file: unknown = typeof raw === 'object' && 'file' in raw ? raw.file : raw;\n if (typeof file === 'string') {\n return file;\n }\n if (file instanceof Uint8Array) {\n return Buffer.from(file).toString('utf8');\n }\n return '';\n}\n\n/**\n * The script a history folder belongs to.\n *\n * @param folder the folder name as `readDir` reports it\n */\nexport function decodeFolderName(folder: string): string {\n try {\n return decodeURIComponent(folder);\n } catch {\n // A folder nobody of ours wrote - report it under its own name rather than throwing\n return folder;\n }\n}\n\n/**\n * How much one history occupies, from its index alone - the stored sources are never read for this.\n *\n * @param index the index of a script\n */\nexport function sumIndex(index: ScriptVersion[]): { versions: number; bytes: number } {\n return {\n versions: index.length,\n bytes: index.reduce((sum, entry) => sum + (entry.size || 0), 0),\n };\n}\n\n/**\n * A size a human can read at a glance.\n *\n * @param bytes the number of bytes\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) {\n return `${bytes} B`;\n }\n if (bytes < 1024 * 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`;\n }\n return `${(bytes / 1024 / 1024).toFixed(2)} MB`;\n}\n\n/**\n * Cut an index down to the current limit, for instance after the setting was made smaller.\n *\n * Follows the same rule as `addVersion`: the newest ones are kept and the very first version\n * survives on top of the quota.\n *\n * @param index the index as it is on disk, newest first\n * @param keep how many versions to keep; `0` drops everything\n */\nexport function trimIndex(index: ScriptVersion[], keep: number): { index: ScriptVersion[]; obsolete: number[] } {\n const sorted = [...index].sort((a, b) => b.ts - a.ts);\n if (!keep) {\n return { index: [], obsolete: sorted.map(v => v.ts) };\n }\n if (sorted.length <= keep) {\n return { index: sorted, obsolete: [] };\n }\n const oldest = sorted[sorted.length - 1];\n return {\n index: [...sorted.slice(0, keep - 1), oldest],\n obsolete: sorted.slice(keep - 1, sorted.length - 1).map(v => v.ts),\n };\n}\n"]}
|