fundus 0.1.0 → 0.3.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 +31 -0
- package/build/client/_app/immutable/assets/0.inkPq3ni.css +1 -0
- package/build/client/_app/immutable/assets/0.inkPq3ni.css.br +0 -0
- package/build/client/_app/immutable/assets/0.inkPq3ni.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.AA5XNK5k.css +1 -0
- package/build/client/_app/immutable/assets/2.AA5XNK5k.css.br +0 -0
- package/build/client/_app/immutable/assets/2.AA5XNK5k.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css +1 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css.br +0 -0
- package/build/client/_app/immutable/assets/3.ChSpCG-T.css.gz +0 -0
- package/build/client/_app/immutable/assets/{LibraryContextMenu.CMefGNE2.css → LibraryContextMenu.CVb-Lqdf.css} +1 -1
- package/build/client/_app/immutable/assets/LibraryContextMenu.CVb-Lqdf.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.CVb-Lqdf.css.gz +0 -0
- package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css +1 -0
- package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css.br +0 -0
- package/build/client/_app/immutable/assets/ManifestSelector.Il8p28r8.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BPJdVAz5.js +1 -0
- package/build/client/_app/immutable/chunks/BPJdVAz5.js.br +0 -0
- package/build/client/_app/immutable/chunks/BPJdVAz5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CLZr1L0m.js +108 -0
- package/build/client/_app/immutable/chunks/CLZr1L0m.js.br +0 -0
- package/build/client/_app/immutable/chunks/CLZr1L0m.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{D9-155C9.js → CLcR3DTB.js} +1 -1
- package/build/client/_app/immutable/chunks/CLcR3DTB.js.br +4 -0
- package/build/client/_app/immutable/chunks/CLcR3DTB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CNRz3sSw.js → D65cSBhe.js} +2 -2
- package/build/client/_app/immutable/chunks/D65cSBhe.js.br +0 -0
- package/build/client/_app/immutable/chunks/D65cSBhe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DkK6dXFi.js +1 -0
- package/build/client/_app/immutable/chunks/DkK6dXFi.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkK6dXFi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DVEDrSOn.js → RVmTMq_g.js} +1 -1
- package/build/client/_app/immutable/chunks/RVmTMq_g.js.br +0 -0
- package/build/client/_app/immutable/chunks/RVmTMq_g.js.gz +0 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js +1 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js.br +0 -0
- package/build/client/_app/immutable/chunks/TZFh8bT7.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.xrZU0FIS.js +2 -0
- package/build/client/_app/immutable/entry/app.xrZU0FIS.js.br +0 -0
- package/build/client/_app/immutable/entry/app.xrZU0FIS.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BT85omvY.js +1 -0
- package/build/client/_app/immutable/entry/start.BT85omvY.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BT85omvY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BjZZ-SXT.js +2 -0
- package/build/client/_app/immutable/nodes/0.BjZZ-SXT.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BjZZ-SXT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.0KLmHZC4.js +1 -0
- package/build/client/_app/immutable/nodes/1.0KLmHZC4.js.br +2 -0
- package/build/client/_app/immutable/nodes/1.0KLmHZC4.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BG8fXI4n.js +3 -0
- package/build/client/_app/immutable/nodes/2.BG8fXI4n.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BG8fXI4n.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DMFh-x1Y.js +1 -0
- package/build/client/_app/immutable/nodes/3.DMFh-x1Y.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DMFh-x1Y.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CsuRmAsC.js +2 -0
- package/build/client/_app/immutable/nodes/4.CsuRmAsC.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CsuRmAsC.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/favicon.svg +4 -0
- package/build/client/favicon.svg.br +0 -0
- package/build/client/favicon.svg.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-CGD-b_vR.js → context.js-B77ZAVLi.js} +401 -31
- package/build/server/chunks/chunks/context.js-B77ZAVLi.js.map +1 -0
- package/build/server/chunks/chunks/{core.js-xfrkaQpS.js → core.js-EA_ysb1J.js} +130 -3
- package/build/server/chunks/chunks/core.js-EA_ysb1J.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-B4E0NJIt.js → exports.js-ScnpVC8o.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-B4E0NJIt.js.map → exports.js-ScnpVC8o.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-Bu-Ao0Vk.js → http.js-DyoR9XRI.js} +5 -3
- package/build/server/chunks/chunks/http.js-DyoR9XRI.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-ClSxY3uX.js → internal.js-D_k3d8Qh.js} +4 -4
- package/build/server/chunks/chunks/{internal.js-ClSxY3uX.js.map → internal.js-D_k3d8Qh.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{library-store.js-CZCDpai0.js → library-store.js-DJ-JRE-g.js} +337 -20
- package/build/server/chunks/chunks/library-store.js-DJ-JRE-g.js.map +1 -0
- package/build/server/chunks/chunks/{shared.js-CHQOW_GN.js → shared.js-MLULcRRJ.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-CHQOW_GN.js.map → shared.js-MLULcRRJ.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-B9L6I-_K.js → utils.js-D64X9Sx8.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-B9L6I-_K.js.map → utils.js-D64X9Sx8.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-PO5RpNzP.js → _server.ts.js-H4Pq1KAE.js} +4 -5
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-PO5RpNzP.js.map → _server.ts.js-H4Pq1KAE.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BmgqGEXs.js → _server.ts.js-BI3KHSfE.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BmgqGEXs.js.map → _server.ts.js-BI3KHSfE.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-DnTOlfHs.js → _server.ts.js-BlNYUbXX.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-DnTOlfHs.js.map → _server.ts.js-BlNYUbXX.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DzdC6OJE.js → _server.ts.js-j-_N540j.js} +9 -7
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-j-_N540j.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DXJNoYAs.js → _server.ts.js-BHKobzAg.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DXJNoYAs.js.map → _server.ts.js-BHKobzAg.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CqdKXoZL.js → _server.ts.js-BFKjIOCv.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CqdKXoZL.js.map → _server.ts.js-BFKjIOCv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DIuY66jG.js → _server.ts.js-BxZeVw8y.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-DIuY66jG.js.map → _server.ts.js-BxZeVw8y.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-D9p-cdJQ.js → _server.ts.js-CO36U_CT.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-D9p-cdJQ.js.map → _server.ts.js-CO36U_CT.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DP1x-xAW.js → _server.ts.js-CBlg1Bkl.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DP1x-xAW.js.map → _server.ts.js-CBlg1Bkl.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-r1eNMvPd.js → _server.ts.js-DcGLCjIs.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-r1eNMvPd.js.map → _server.ts.js-DcGLCjIs.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/_server.ts.js-AqhtjOiL.js +32 -0
- package/build/server/chunks/entries/endpoints/api/raw/adopt/_server.ts.js-AqhtjOiL.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/_server.ts.js-CSa6-DcU.js +30 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/_server.ts.js-CSa6-DcU.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/_server.ts.js-DnzZUoAo.js +30 -0
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/_server.ts.js-DnzZUoAo.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/reconcile/_server.ts.js-B0KMprKq.js +33 -0
- package/build/server/chunks/entries/endpoints/api/reconcile/_server.ts.js-B0KMprKq.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/reveal/_server.ts.js-Dhj5O9Lv.js +75 -0
- package/build/server/chunks/entries/endpoints/api/reveal/_server.ts.js-Dhj5O9Lv.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D-m3BwRj.js → _server.ts.js-B1N9o1Ne.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D-m3BwRj.js.map → _server.ts.js-B1N9o1Ne.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-3nyAQ3f0.js → _server.ts.js-B6HbnRqq.js} +4 -5
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-3nyAQ3f0.js.map → _server.ts.js-B6HbnRqq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Ba285FiJ.js → _server.ts.js-7wzPyG9v.js} +5 -6
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Ba285FiJ.js.map → _server.ts.js-7wzPyG9v.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-X354l2NP.js → _server.ts.js-BWztxYge.js} +5 -6
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-X354l2NP.js.map → _server.ts.js-BWztxYge.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-eZ7H27L4.js → hooks.server.js-CVKLksJ1.js} +4 -5
- package/build/server/chunks/entries/{hooks.server.js-eZ7H27L4.js.map → hooks.server.js-CVKLksJ1.js.map} +1 -1
- package/build/server/chunks/{handler-Dcc_-rVh.js → handler-C22elTeC.js} +5 -5
- package/build/server/chunks/{handler-Dcc_-rVh.js.map → handler-C22elTeC.js.map} +1 -1
- package/build/server/chunks/{index.js-DoVHd4mf.js → index.js-CqN1-wfe.js} +5 -5
- package/build/server/chunks/{index.js-DoVHd4mf.js.map → index.js-CqN1-wfe.js.map} +1 -1
- package/build/server/chunks/{manifest.js-C_MsDhaa.js → manifest.js-5SY_cfDE.js} +58 -23
- package/build/server/chunks/manifest.js-5SY_cfDE.js.map +1 -0
- package/build/server/chunks/nodes/0.js-CoD1zSiQ.js +11 -0
- package/build/server/chunks/nodes/0.js-CoD1zSiQ.js.map +1 -0
- package/build/server/chunks/nodes/1.js-B92oHDrL.js +7 -0
- package/build/server/chunks/nodes/{1.js-DKlcrAQP.js.map → 1.js-B92oHDrL.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-COlJ22cI.js +7 -0
- package/build/server/chunks/nodes/2.js-COlJ22cI.js.map +1 -0
- package/build/server/chunks/nodes/3.js-C1yCh8Tq.js +7 -0
- package/build/server/chunks/nodes/3.js-C1yCh8Tq.js.map +1 -0
- package/build/server/chunks/nodes/4.js-B9BEDKwq.js +7 -0
- package/build/server/chunks/nodes/{4.js-JQQZnHWZ.js.map → 4.js-B9BEDKwq.js.map} +1 -1
- package/dist/core/api-types.d.ts +38 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +1 -0
- package/dist/core/raw-path.d.ts +8 -0
- package/dist/core/raw-path.js +63 -0
- package/dist/server/build.js +11 -2
- package/dist/server/check.d.ts +5 -1
- package/dist/server/check.js +80 -28
- package/dist/server/cli-commands.d.ts +1 -1
- package/dist/server/cli-commands.js +5 -3
- package/dist/server/cli-init.js +16 -1
- package/dist/server/file-manager.d.ts +2 -0
- package/dist/server/file-manager.js +17 -0
- package/dist/server/http.js +5 -1
- package/dist/server/library-store.d.ts +12 -0
- package/dist/server/library-store.js +25 -14
- package/dist/server/operations.d.ts +9 -1
- package/dist/server/operations.js +387 -31
- package/dist/server/processing/processor.js +3 -0
- package/dist/server/raw-reconciliation.d.ts +53 -0
- package/dist/server/raw-reconciliation.js +338 -0
- package/dist/server/raw-safety.d.ts +10 -0
- package/dist/server/raw-safety.js +73 -0
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/0.DLhIsYA_.css +0 -1
- package/build/client/_app/immutable/assets/0.DLhIsYA_.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DLhIsYA_.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.ohi-TClG.css +0 -1
- package/build/client/_app/immutable/assets/2.ohi-TClG.css.br +0 -0
- package/build/client/_app/immutable/assets/2.ohi-TClG.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.C-toFWI4.css +0 -1
- package/build/client/_app/immutable/assets/3.C-toFWI4.css.br +0 -0
- package/build/client/_app/immutable/assets/3.C-toFWI4.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.CMefGNE2.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BA62n9FX.js +0 -108
- package/build/client/_app/immutable/chunks/BA62n9FX.js.br +0 -0
- package/build/client/_app/immutable/chunks/BA62n9FX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CNRz3sSw.js.br +0 -0
- package/build/client/_app/immutable/chunks/CNRz3sSw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CQsKwm8E.js +0 -1
- package/build/client/_app/immutable/chunks/CQsKwm8E.js.br +0 -0
- package/build/client/_app/immutable/chunks/CQsKwm8E.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D9-155C9.js.br +0 -0
- package/build/client/_app/immutable/chunks/D9-155C9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DVEDrSOn.js.br +0 -0
- package/build/client/_app/immutable/chunks/DVEDrSOn.js.gz +0 -0
- package/build/client/_app/immutable/chunks/cznBUb-E.js +0 -1
- package/build/client/_app/immutable/chunks/cznBUb-E.js.br +0 -0
- package/build/client/_app/immutable/chunks/cznBUb-E.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.DwQWZs1F.js +0 -2
- package/build/client/_app/immutable/entry/app.DwQWZs1F.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DwQWZs1F.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DEpAL_8Q.js +0 -1
- package/build/client/_app/immutable/entry/start.DEpAL_8Q.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DEpAL_8Q.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Sr1WDgiY.js +0 -2
- package/build/client/_app/immutable/nodes/0.Sr1WDgiY.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Sr1WDgiY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BwHqv728.js +0 -1
- package/build/client/_app/immutable/nodes/1.BwHqv728.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.BwHqv728.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DidxYIU-.js +0 -3
- package/build/client/_app/immutable/nodes/2.DidxYIU-.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DidxYIU-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.C1AavoTf.js +0 -1
- package/build/client/_app/immutable/nodes/3.C1AavoTf.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.C1AavoTf.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.D8UoOONM.js +0 -2
- package/build/client/_app/immutable/nodes/4.D8UoOONM.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.D8UoOONM.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-CGD-b_vR.js.map +0 -1
- package/build/server/chunks/chunks/core.js-xfrkaQpS.js.map +0 -1
- package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js +0 -64
- package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js.map +0 -1
- package/build/server/chunks/chunks/http.js-Bu-Ao0Vk.js.map +0 -1
- package/build/server/chunks/chunks/library-store.js-CZCDpai0.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-DzdC6OJE.js.map +0 -1
- package/build/server/chunks/manifest.js-C_MsDhaa.js.map +0 -1
- package/build/server/chunks/nodes/0.js-BP6hHAK9.js +0 -11
- package/build/server/chunks/nodes/0.js-BP6hHAK9.js.map +0 -1
- package/build/server/chunks/nodes/1.js-DKlcrAQP.js +0 -7
- package/build/server/chunks/nodes/2.js-B-8TTZH_.js +0 -7
- package/build/server/chunks/nodes/2.js-B-8TTZH_.js.map +0 -1
- package/build/server/chunks/nodes/3.js-Cf7H6K5p.js +0 -7
- package/build/server/chunks/nodes/3.js-Cf7H6K5p.js.map +0 -1
- package/build/server/chunks/nodes/4.js-JQQZnHWZ.js +0 -7
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
* operation has exactly one implementation and there is no other save path.
|
|
7
7
|
*/
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import { allDeliveredAssetIds, collectAllReferences, compareCodeUnits, computeLibraryMembership, folderOfRawPath, rawFileNameOfPath } from '../core/index.js';
|
|
9
|
+
import { assetIdError, allDeliveredAssetIds, collectAllReferences, compareCodeUnits, computeLibraryMembership, folderOfRawPath, parentFolderPath, portableFolderPathKey, proposeAssetId, rawFileNameOfPath, typesForFileName } from '../core/index.js';
|
|
10
10
|
import { LibraryStore } from './library-store.js';
|
|
11
11
|
import { generateManifestModules } from './codegen.js';
|
|
12
12
|
import { descriptorsByType, serverPluginForType, serverPluginsByType } from './plugins.js';
|
|
13
13
|
import { processAssets, readCurrentProxy } from './processing/processor.js';
|
|
14
14
|
import { encodeRelativePathForUrl } from './folder-filesystem.js';
|
|
15
|
+
import { classifyRawDirectory, verifyStableRawFile } from './raw-reconciliation.js';
|
|
16
|
+
import { rawSafetyIssues } from './raw-safety.js';
|
|
15
17
|
/**
|
|
16
18
|
* The CLI's `--parameters` merge semantics (M7): a SHALLOW merge at the top
|
|
17
19
|
* level onto the current parameters — a nested object value replaces the whole
|
|
@@ -42,6 +44,8 @@ export class FundusOperations {
|
|
|
42
44
|
#config;
|
|
43
45
|
#registry;
|
|
44
46
|
#store;
|
|
47
|
+
#operationQueue = Promise.resolve();
|
|
48
|
+
#pipelineQueue = Promise.resolve();
|
|
45
49
|
constructor(config, registry = serverPluginsByType) {
|
|
46
50
|
this.#config = config;
|
|
47
51
|
this.#registry = registry;
|
|
@@ -53,9 +57,171 @@ export class FundusOperations {
|
|
|
53
57
|
get store() {
|
|
54
58
|
return this.#store;
|
|
55
59
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
#enqueueOperation(operation) {
|
|
61
|
+
const run = this.#operationQueue.then(operation, operation);
|
|
62
|
+
this.#operationQueue = run.then(() => { }, () => { });
|
|
63
|
+
return run;
|
|
64
|
+
}
|
|
65
|
+
/** Read-only state snapshot, serialized with mutations but never processing or writing. */
|
|
66
|
+
state() {
|
|
67
|
+
return this.#enqueueOperation(() => this.#state());
|
|
68
|
+
}
|
|
69
|
+
async #state() {
|
|
70
|
+
await this.#pipelineQueue;
|
|
71
|
+
const library = await this.#store.read();
|
|
72
|
+
const classification = await this.#classifyRawDirectory(library);
|
|
73
|
+
return this.#snapshotState(library, classification);
|
|
74
|
+
}
|
|
75
|
+
/** Reconcile only changes that are safe without a user decision: unique moves and deletions. */
|
|
76
|
+
reconcileRawState() {
|
|
77
|
+
return this.#enqueueOperation(async () => {
|
|
78
|
+
let library = await this.#store.read();
|
|
79
|
+
let classification = await this.#classifyRawDirectory(library);
|
|
80
|
+
if (!this.#canAutoReconcile(classification)) {
|
|
81
|
+
return this.#snapshotState(library, classification);
|
|
82
|
+
}
|
|
83
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
84
|
+
const result = await this.#store.transactCandidate(async (candidate, current) => {
|
|
85
|
+
const fresh = await this.#classifyRawDirectory(current);
|
|
86
|
+
const affectedIds = [];
|
|
87
|
+
if (!this.#canAutoReconcile(fresh))
|
|
88
|
+
return { classification: fresh, affectedIds };
|
|
89
|
+
for (const move of fresh.moves) {
|
|
90
|
+
candidate.assets[move.assetId].raw = move.path;
|
|
91
|
+
this.#registerFolderAncestors(candidate, folderOfRawPath(move.path));
|
|
92
|
+
affectedIds.push(move.assetId);
|
|
93
|
+
}
|
|
94
|
+
for (const deletion of fresh.deletions) {
|
|
95
|
+
delete candidate.assets[deletion.assetId];
|
|
96
|
+
affectedIds.push(deletion.assetId);
|
|
97
|
+
}
|
|
98
|
+
return { classification: fresh, affectedIds };
|
|
99
|
+
});
|
|
100
|
+
library = result.library;
|
|
101
|
+
classification = result.value.classification;
|
|
102
|
+
if (!result.changed)
|
|
103
|
+
return this.#snapshotState(library, classification);
|
|
104
|
+
await this.#store.syncFolders(library);
|
|
105
|
+
for (const id of result.value.affectedIds)
|
|
106
|
+
await this.#store.deleteProxiesOf(id);
|
|
107
|
+
const snapshot = await this.#afterMutationSnapshot(library);
|
|
108
|
+
library = snapshot.library;
|
|
109
|
+
classification = snapshot.classification;
|
|
110
|
+
if (classification.moves.length === 0 && classification.deletions.length === 0) {
|
|
111
|
+
return snapshot.state;
|
|
112
|
+
}
|
|
113
|
+
if (!this.#canAutoReconcile(classification, true))
|
|
114
|
+
return snapshot.state;
|
|
115
|
+
}
|
|
116
|
+
return this.#snapshotState(library, classification, [
|
|
117
|
+
{
|
|
118
|
+
message: 'Raw directory kept changing during reconciliation; refresh again after external file operations settle.'
|
|
119
|
+
}
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Adopt an already-existing raw file without copying, renaming, or deleting it. */
|
|
124
|
+
adoptRawFile(token, id, type, manifests = []) {
|
|
125
|
+
return this.#enqueueOperation(async () => {
|
|
126
|
+
const result = await this.#store.transactCandidate(async (candidate, current) => {
|
|
127
|
+
const classification = await this.#classifyRawDirectory(current);
|
|
128
|
+
const file = classification.unmanaged.find((entry) => this.#newToken(entry.path, entry.hash) === token);
|
|
129
|
+
if (!file)
|
|
130
|
+
throw new Error('This ingestion item is stale; refresh the editor and try again.');
|
|
131
|
+
const idProblem = assetIdError(id);
|
|
132
|
+
if (idProblem)
|
|
133
|
+
throw new Error(idProblem);
|
|
134
|
+
if (Object.hasOwn(candidate.assets, id))
|
|
135
|
+
throw new Error(`An asset with id "${id}" already exists.`);
|
|
136
|
+
const compatibleTypes = this.#compatibleTypes(file.path);
|
|
137
|
+
if (!compatibleTypes.includes(type)) {
|
|
138
|
+
throw new Error(`The ${type} type does not accept "${file.path}".`);
|
|
139
|
+
}
|
|
140
|
+
const uniqueManifests = [...new Set(manifests)];
|
|
141
|
+
for (const name of uniqueManifests) {
|
|
142
|
+
if (!Object.hasOwn(candidate.manifests, name)) {
|
|
143
|
+
throw new Error(`Unknown manifest "${name}".`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const plugin = serverPluginForType(type, this.#registry);
|
|
147
|
+
let parameters = plugin.descriptor.defaultParameters(plugin.pluginConfig(this.#config));
|
|
148
|
+
parameters = plugin.descriptor.validateParameters(parameters);
|
|
149
|
+
if (plugin.seedIngestParameters) {
|
|
150
|
+
const seeded = await plugin.seedIngestParameters({
|
|
151
|
+
rawPath: join(this.#config.rawDir, file.path),
|
|
152
|
+
rawFileName: rawFileNameOfPath(file.path),
|
|
153
|
+
parameters,
|
|
154
|
+
config: this.#config
|
|
155
|
+
});
|
|
156
|
+
if (seeded !== undefined)
|
|
157
|
+
parameters = plugin.descriptor.validateParameters(seeded);
|
|
158
|
+
}
|
|
159
|
+
await verifyStableRawFile(join(this.#config.rawDir, file.path), file.hash);
|
|
160
|
+
this.#registerFolderAncestors(candidate, folderOfRawPath(file.path));
|
|
161
|
+
candidate.assets[id] = {
|
|
162
|
+
type,
|
|
163
|
+
raw: file.path,
|
|
164
|
+
rawHash: file.hash,
|
|
165
|
+
parameters,
|
|
166
|
+
manifests: uniqueManifests.sort()
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
await this.#store.syncFolders(result.library);
|
|
170
|
+
return this.#afterMutation(result.library);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** Reset/reseed edited bytes, optionally changing to another compatible type. */
|
|
174
|
+
acceptEditedRawFile(token, type) {
|
|
175
|
+
return this.#enqueueOperation(async () => {
|
|
176
|
+
const result = await this.#store.transactCandidate(async (candidate, current) => {
|
|
177
|
+
const classification = await this.#classifyRawDirectory(current);
|
|
178
|
+
const edited = this.#editedRawFiles(classification, current).find((entry) => this.#editedToken(entry.assetId, entry.path, entry.hash) === token);
|
|
179
|
+
if (!edited)
|
|
180
|
+
throw new Error('This edited item is stale; refresh the editor and try again.');
|
|
181
|
+
if (!this.#compatibleTypes(edited.path).includes(type)) {
|
|
182
|
+
throw new Error(`The ${type} type does not accept "${edited.path}".`);
|
|
183
|
+
}
|
|
184
|
+
const plugin = serverPluginForType(type, this.#registry);
|
|
185
|
+
let parameters = plugin.descriptor.validateParameters(plugin.descriptor.defaultParameters(plugin.pluginConfig(this.#config)));
|
|
186
|
+
if (plugin.seedIngestParameters) {
|
|
187
|
+
const seeded = await plugin.seedIngestParameters({
|
|
188
|
+
rawPath: join(this.#config.rawDir, edited.path),
|
|
189
|
+
rawFileName: rawFileNameOfPath(edited.path),
|
|
190
|
+
parameters,
|
|
191
|
+
config: this.#config
|
|
192
|
+
});
|
|
193
|
+
if (seeded !== undefined)
|
|
194
|
+
parameters = plugin.descriptor.validateParameters(seeded);
|
|
195
|
+
}
|
|
196
|
+
await verifyStableRawFile(join(this.#config.rawDir, edited.path), edited.hash);
|
|
197
|
+
const record = candidate.assets[edited.assetId];
|
|
198
|
+
record.type = type;
|
|
199
|
+
record.rawHash = edited.hash;
|
|
200
|
+
record.parameters = parameters;
|
|
201
|
+
return edited.assetId;
|
|
202
|
+
});
|
|
203
|
+
await this.#store.deleteProxiesOf(result.value);
|
|
204
|
+
return this.#afterMutation(result.library);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Accept edited bytes while preserving type, parameters, path and memberships. */
|
|
208
|
+
dismissEditedRawFile(token) {
|
|
209
|
+
return this.#enqueueOperation(async () => {
|
|
210
|
+
const result = await this.#store.transactCandidate(async (candidate, current) => {
|
|
211
|
+
const classification = await this.#classifyRawDirectory(current);
|
|
212
|
+
const edited = this.#editedRawFiles(classification, current).find((entry) => this.#editedToken(entry.assetId, entry.path, entry.hash) === token);
|
|
213
|
+
if (!edited)
|
|
214
|
+
throw new Error('This edited item is stale; refresh the editor and try again.');
|
|
215
|
+
const record = current.assets[edited.assetId];
|
|
216
|
+
if (!this.#compatibleTypes(edited.path).includes(record.type)) {
|
|
217
|
+
throw new Error(`The moved file "${edited.path}" is not compatible with its current ${record.type} type; choose a compatible type and process it.`);
|
|
218
|
+
}
|
|
219
|
+
candidate.assets[edited.assetId].rawHash = edited.hash;
|
|
220
|
+
return edited.assetId;
|
|
221
|
+
});
|
|
222
|
+
await this.#store.deleteProxiesOf(result.value);
|
|
223
|
+
return this.#afterMutation(result.library);
|
|
224
|
+
});
|
|
59
225
|
}
|
|
60
226
|
/**
|
|
61
227
|
* Ingest an upload: store the raw file + record, then let the owning
|
|
@@ -66,7 +232,10 @@ export class FundusOperations {
|
|
|
66
232
|
* explicit membership set in the same operation; a failing application
|
|
67
233
|
* rolls the whole ingest back, so a failed ingest leaves nothing behind.
|
|
68
234
|
*/
|
|
69
|
-
|
|
235
|
+
ingest(options) {
|
|
236
|
+
return this.#enqueueOperation(() => this.#ingest(options));
|
|
237
|
+
}
|
|
238
|
+
async #ingest(options) {
|
|
70
239
|
let library = await this.#store.ingest(options);
|
|
71
240
|
try {
|
|
72
241
|
const plugin = serverPluginForType(options.type, this.#registry);
|
|
@@ -96,7 +265,10 @@ export class FundusOperations {
|
|
|
96
265
|
return this.#afterMutation(library);
|
|
97
266
|
}
|
|
98
267
|
/** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
|
|
99
|
-
|
|
268
|
+
patchAsset(id, body) {
|
|
269
|
+
return this.#enqueueOperation(() => this.#patchAsset(id, body));
|
|
270
|
+
}
|
|
271
|
+
async #patchAsset(id, body) {
|
|
100
272
|
let library = await this.#store.read();
|
|
101
273
|
this.#requireAsset(library, id);
|
|
102
274
|
if (body.parameters !== undefined) {
|
|
@@ -122,15 +294,24 @@ export class FundusOperations {
|
|
|
122
294
|
}
|
|
123
295
|
return this.#afterMutation(library);
|
|
124
296
|
}
|
|
125
|
-
|
|
297
|
+
moveAsset(id, folder) {
|
|
298
|
+
return this.#enqueueOperation(() => this.#moveAsset(id, folder));
|
|
299
|
+
}
|
|
300
|
+
async #moveAsset(id, folder) {
|
|
126
301
|
return this.#afterMutation(await this.#store.moveAsset(id, folder));
|
|
127
302
|
}
|
|
128
303
|
/** Replace the raw bytes/name and run the normal processing + codegen tail. */
|
|
129
|
-
|
|
304
|
+
replaceRawFile(id, options) {
|
|
305
|
+
return this.#enqueueOperation(() => this.#replaceRawFile(id, options));
|
|
306
|
+
}
|
|
307
|
+
async #replaceRawFile(id, options) {
|
|
130
308
|
return this.#afterMutation(await this.#store.replaceRawFile(id, options));
|
|
131
309
|
}
|
|
132
310
|
/** Move a selected asset set as one editor action and roll back on any failure. */
|
|
133
|
-
|
|
311
|
+
moveAssets(assetIds, folder) {
|
|
312
|
+
return this.#enqueueOperation(() => this.#moveAssets(assetIds, folder));
|
|
313
|
+
}
|
|
314
|
+
async #moveAssets(assetIds, folder) {
|
|
134
315
|
const uniqueIds = [...new Set(assetIds)];
|
|
135
316
|
if (uniqueIds.length === 0)
|
|
136
317
|
throw new Error('At least one asset id is required.');
|
|
@@ -158,16 +339,28 @@ export class FundusOperations {
|
|
|
158
339
|
throw cause;
|
|
159
340
|
}
|
|
160
341
|
}
|
|
161
|
-
|
|
342
|
+
createFolder(path) {
|
|
343
|
+
return this.#enqueueOperation(() => this.#createFolder(path));
|
|
344
|
+
}
|
|
345
|
+
async #createFolder(path) {
|
|
162
346
|
return this.#afterMutation(await this.#store.createFolder(path));
|
|
163
347
|
}
|
|
164
|
-
|
|
348
|
+
renameFolder(path, newName) {
|
|
349
|
+
return this.#enqueueOperation(() => this.#renameFolder(path, newName));
|
|
350
|
+
}
|
|
351
|
+
async #renameFolder(path, newName) {
|
|
165
352
|
return this.#afterMutation(await this.#store.renameFolder(path, newName));
|
|
166
353
|
}
|
|
167
|
-
|
|
354
|
+
moveFolder(path, parent) {
|
|
355
|
+
return this.#enqueueOperation(() => this.#moveFolder(path, parent));
|
|
356
|
+
}
|
|
357
|
+
async #moveFolder(path, parent) {
|
|
168
358
|
return this.#afterMutation(await this.#store.moveFolder(path, parent));
|
|
169
359
|
}
|
|
170
|
-
|
|
360
|
+
deleteFolder(path) {
|
|
361
|
+
return this.#enqueueOperation(() => this.#deleteFolder(path));
|
|
362
|
+
}
|
|
363
|
+
async #deleteFolder(path) {
|
|
171
364
|
const result = await this.#store.deleteFolder(path);
|
|
172
365
|
return this.#afterMutation(result.library);
|
|
173
366
|
}
|
|
@@ -179,7 +372,10 @@ export class FundusOperations {
|
|
|
179
372
|
* rejected with nothing written. The REST `patchAsset` keeps the editor's
|
|
180
373
|
* lenient replace semantics.
|
|
181
374
|
*/
|
|
182
|
-
|
|
375
|
+
setAsset(id, changes) {
|
|
376
|
+
return this.#enqueueOperation(() => this.#setAsset(id, changes));
|
|
377
|
+
}
|
|
378
|
+
async #setAsset(id, changes) {
|
|
183
379
|
const library = await this.#store.read();
|
|
184
380
|
this.#requireAsset(library, id);
|
|
185
381
|
return this.#afterMutation(await this.#applyAssetChanges(library, id, changes));
|
|
@@ -189,7 +385,10 @@ export class FundusOperations {
|
|
|
189
385
|
* a referrer keeps pointing at the old id, so renaming a referenced asset
|
|
190
386
|
* introduces dangling-reference errors. Update the referrers first.
|
|
191
387
|
*/
|
|
192
|
-
|
|
388
|
+
renameAsset(id, newId) {
|
|
389
|
+
return this.#enqueueOperation(() => this.#renameAsset(id, newId));
|
|
390
|
+
}
|
|
391
|
+
async #renameAsset(id, newId) {
|
|
193
392
|
const library = await this.#store.read();
|
|
194
393
|
this.#requireAsset(library, id);
|
|
195
394
|
// A collision or invalid new id is the store's own (clearer) error; the
|
|
@@ -208,7 +407,10 @@ export class FundusOperations {
|
|
|
208
407
|
* (the asset is a reference target) is rejected before anything —
|
|
209
408
|
* including the raw file — is removed; the REST route stays lenient.
|
|
210
409
|
*/
|
|
211
|
-
|
|
410
|
+
deleteAsset(id, options) {
|
|
411
|
+
return this.#enqueueOperation(() => this.#deleteAsset(id, options));
|
|
412
|
+
}
|
|
413
|
+
async #deleteAsset(id, options) {
|
|
212
414
|
if (options?.failOnIntroducedIssues) {
|
|
213
415
|
const library = await this.#store.read();
|
|
214
416
|
this.#requireAsset(library, id);
|
|
@@ -219,20 +421,32 @@ export class FundusOperations {
|
|
|
219
421
|
return this.#afterMutation(await this.#store.deleteAsset(id));
|
|
220
422
|
}
|
|
221
423
|
/** Delete a selected asset set as one lenient editor mutation. */
|
|
222
|
-
|
|
424
|
+
deleteAssets(assetIds) {
|
|
425
|
+
return this.#enqueueOperation(() => this.#deleteAssets(assetIds));
|
|
426
|
+
}
|
|
427
|
+
async #deleteAssets(assetIds) {
|
|
223
428
|
const uniqueIds = [...new Set(assetIds)];
|
|
224
429
|
if (uniqueIds.length === 0)
|
|
225
430
|
throw new Error('At least one asset id is required.');
|
|
226
431
|
return this.#afterMutation(await this.#store.deleteAssets(uniqueIds));
|
|
227
432
|
}
|
|
228
|
-
|
|
433
|
+
createManifest(name) {
|
|
434
|
+
return this.#enqueueOperation(() => this.#createManifest(name));
|
|
435
|
+
}
|
|
436
|
+
async #createManifest(name) {
|
|
229
437
|
return this.#afterMutation(await this.#store.createManifest(name));
|
|
230
438
|
}
|
|
231
|
-
|
|
439
|
+
setManifestAppearance(name, appearance) {
|
|
440
|
+
return this.#enqueueOperation(() => this.#setManifestAppearance(name, appearance));
|
|
441
|
+
}
|
|
442
|
+
async #setManifestAppearance(name, appearance) {
|
|
232
443
|
return this.#afterMutation(await this.#store.setManifestAppearance(name, appearance));
|
|
233
444
|
}
|
|
234
445
|
/** Apply a PATCH body: appearance fields and/or a rename — in that order. */
|
|
235
|
-
|
|
446
|
+
patchManifest(name, body) {
|
|
447
|
+
return this.#enqueueOperation(() => this.#patchManifest(name, body));
|
|
448
|
+
}
|
|
449
|
+
async #patchManifest(name, body) {
|
|
236
450
|
let library = await this.#store.setManifestAppearance(name, {
|
|
237
451
|
color: body.color,
|
|
238
452
|
emoji: body.emoji
|
|
@@ -251,17 +465,33 @@ export class FundusOperations {
|
|
|
251
465
|
* the module must be updated by hand — both the file name and the exported
|
|
252
466
|
* const change with the manifest name.
|
|
253
467
|
*/
|
|
254
|
-
|
|
468
|
+
renameManifest(name, newName) {
|
|
469
|
+
return this.#enqueueOperation(() => this.#renameManifest(name, newName));
|
|
470
|
+
}
|
|
471
|
+
async #renameManifest(name, newName) {
|
|
255
472
|
return this.#afterMutation(await this.#store.renameManifest(name, newName));
|
|
256
473
|
}
|
|
257
|
-
|
|
474
|
+
deleteManifest(name) {
|
|
475
|
+
return this.#enqueueOperation(() => this.#deleteManifest(name));
|
|
476
|
+
}
|
|
477
|
+
async #deleteManifest(name) {
|
|
258
478
|
return this.#afterMutation(await this.#store.deleteManifest(name));
|
|
259
479
|
}
|
|
260
480
|
/** The autosave chain tail: process + regenerate, then report fresh state. */
|
|
261
481
|
async #afterMutation(library) {
|
|
482
|
+
return (await this.#afterMutationSnapshot(library)).state;
|
|
483
|
+
}
|
|
484
|
+
async #afterMutationSnapshot(library) {
|
|
485
|
+
const run = this.#pipelineQueue.then(() => this.#runAfterMutation(library), () => this.#runAfterMutation(library));
|
|
486
|
+
this.#pipelineQueue = run.then(() => { }, () => { });
|
|
487
|
+
return run;
|
|
488
|
+
}
|
|
489
|
+
async #runAfterMutation(library) {
|
|
262
490
|
const pipelineIssues = [];
|
|
263
|
-
const
|
|
264
|
-
|
|
491
|
+
const classification = await this.#classifyRawDirectory(library);
|
|
492
|
+
const ordinaryIssues = await this.#store.validate(library);
|
|
493
|
+
const safetyIssues = this.#rawSafetyIssues(library, classification);
|
|
494
|
+
if (ordinaryIssues.length === 0 && safetyIssues.length === 0) {
|
|
265
495
|
try {
|
|
266
496
|
const report = await processAssets(this.#config, library, this.#registry);
|
|
267
497
|
await generateManifestModules(this.#config, library, report.proxies, this.#registry);
|
|
@@ -272,13 +502,15 @@ export class FundusOperations {
|
|
|
272
502
|
});
|
|
273
503
|
}
|
|
274
504
|
}
|
|
275
|
-
|
|
505
|
+
const latestLibrary = await this.#store.read();
|
|
506
|
+
const latestClassification = await this.#classifyRawDirectory(latestLibrary);
|
|
507
|
+
return {
|
|
508
|
+
library: latestLibrary,
|
|
509
|
+
classification: latestClassification,
|
|
510
|
+
state: await this.#snapshotState(latestLibrary, latestClassification, pipelineIssues)
|
|
511
|
+
};
|
|
276
512
|
}
|
|
277
|
-
async #buildState(library,
|
|
278
|
-
const issues = [
|
|
279
|
-
...(precomputedIssues ?? (await this.#store.validate(library))),
|
|
280
|
-
...extraIssues
|
|
281
|
-
];
|
|
513
|
+
async #buildState(library, issues, classification) {
|
|
282
514
|
const referencesByAsset = collectAllReferences(library, descriptorsByType(this.#registry));
|
|
283
515
|
const membershipByManifest = computeLibraryMembership(library, referencesByAsset);
|
|
284
516
|
const delivered = allDeliveredAssetIds(membershipByManifest);
|
|
@@ -378,15 +610,139 @@ export class FundusOperations {
|
|
|
378
610
|
for (const [type, plugin] of Object.entries(this.#registry)) {
|
|
379
611
|
presetNames[type] = Object.keys(plugin.presets(this.#config)).sort();
|
|
380
612
|
}
|
|
613
|
+
const rawIngestionQueue = this.#buildRawIngestionQueue(library, classification);
|
|
381
614
|
return {
|
|
382
615
|
library,
|
|
383
616
|
assets,
|
|
384
617
|
issues,
|
|
385
618
|
presetNames,
|
|
386
619
|
manifestMemberships,
|
|
387
|
-
manifestDeliveredBytes
|
|
620
|
+
manifestDeliveredBytes,
|
|
621
|
+
rawIngestionQueue
|
|
388
622
|
};
|
|
389
623
|
}
|
|
624
|
+
#compatibleTypes(path) {
|
|
625
|
+
return typesForFileName(path, Object.values(this.#registry).map((plugin) => plugin.descriptor)).sort(compareCodeUnits);
|
|
626
|
+
}
|
|
627
|
+
#classifyRawDirectory(library) {
|
|
628
|
+
return classifyRawDirectory(this.#config.rawDir, library, (path) => this.#compatibleTypes(path).length > 0);
|
|
629
|
+
}
|
|
630
|
+
#rawSafetyIssues(library, classification) {
|
|
631
|
+
return rawSafetyIssues(library, classification, (path) => this.#compatibleTypes(path));
|
|
632
|
+
}
|
|
633
|
+
async #snapshotState(library, classification, extraIssues = []) {
|
|
634
|
+
const issues = [
|
|
635
|
+
...(await this.#store.validate(library)),
|
|
636
|
+
...this.#rawSafetyIssues(library, classification),
|
|
637
|
+
...extraIssues
|
|
638
|
+
];
|
|
639
|
+
const seen = new Set();
|
|
640
|
+
const uniqueIssues = issues.filter((issue) => {
|
|
641
|
+
const key = `${issue.assetId ?? ''}|${issue.message}`;
|
|
642
|
+
if (seen.has(key))
|
|
643
|
+
return false;
|
|
644
|
+
seen.add(key);
|
|
645
|
+
return true;
|
|
646
|
+
});
|
|
647
|
+
return this.#buildState(library, uniqueIssues, classification);
|
|
648
|
+
}
|
|
649
|
+
#canAutoReconcile(classification, retry = false) {
|
|
650
|
+
return (classification.complete &&
|
|
651
|
+
classification.blockers.length === 0 &&
|
|
652
|
+
classification.moves.length + classification.deletions.length > 0 &&
|
|
653
|
+
(!retry || (classification.edited.length === 0 && classification.unmanaged.length === 0)));
|
|
654
|
+
}
|
|
655
|
+
#newToken(path, hash) {
|
|
656
|
+
return `new:${path}:${hash}`;
|
|
657
|
+
}
|
|
658
|
+
#editedToken(assetId, path, hash) {
|
|
659
|
+
return `edited:${assetId}:${path}:${hash}`;
|
|
660
|
+
}
|
|
661
|
+
#proposedId(path, reservedIds) {
|
|
662
|
+
const base = proposeAssetId(rawFileNameOfPath(path));
|
|
663
|
+
if (!reservedIds.has(base))
|
|
664
|
+
return base;
|
|
665
|
+
for (let index = 2;; index++) {
|
|
666
|
+
const candidate = `${base}${index}`;
|
|
667
|
+
if (!reservedIds.has(candidate))
|
|
668
|
+
return candidate;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
#buildRawIngestionQueue(library, classification) {
|
|
672
|
+
const queue = [];
|
|
673
|
+
const reservedIds = new Set(Object.keys(library.assets));
|
|
674
|
+
for (const file of classification.unmanaged) {
|
|
675
|
+
const compatibleTypes = this.#compatibleTypes(file.path);
|
|
676
|
+
const proposedId = this.#proposedId(file.path, reservedIds);
|
|
677
|
+
reservedIds.add(proposedId);
|
|
678
|
+
queue.push({
|
|
679
|
+
kind: 'new',
|
|
680
|
+
token: this.#newToken(file.path, file.hash),
|
|
681
|
+
path: file.path,
|
|
682
|
+
hash: file.hash,
|
|
683
|
+
compatibleTypes,
|
|
684
|
+
proposedId
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
for (const edited of this.#editedRawFiles(classification, library)) {
|
|
688
|
+
const currentType = library.assets[edited.assetId].type;
|
|
689
|
+
queue.push({
|
|
690
|
+
kind: 'edited',
|
|
691
|
+
token: this.#editedToken(edited.assetId, edited.path, edited.hash),
|
|
692
|
+
path: edited.path,
|
|
693
|
+
hash: edited.hash,
|
|
694
|
+
assetId: edited.assetId,
|
|
695
|
+
currentType,
|
|
696
|
+
compatibleTypes: this.#compatibleTypes(edited.path)
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
for (const blocker of classification.blockers) {
|
|
700
|
+
queue.push({
|
|
701
|
+
kind: 'unsupported',
|
|
702
|
+
token: `unsupported:${blocker.path}:${blocker.reason}`,
|
|
703
|
+
path: blocker.path,
|
|
704
|
+
reason: blocker.reason
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
for (const diagnostic of classification.diagnostics) {
|
|
708
|
+
queue.push({
|
|
709
|
+
kind: 'unsupported',
|
|
710
|
+
token: `unsupported:raw-directory:${diagnostic.reason}`,
|
|
711
|
+
path: diagnostic.path || 'Raw directory',
|
|
712
|
+
reason: diagnostic.reason
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return queue.sort((a, b) => compareCodeUnits(a.path, b.path));
|
|
716
|
+
}
|
|
717
|
+
#editedRawFiles(classification, library) {
|
|
718
|
+
const edited = new Map(classification.edited.map((entry) => [entry.assetId, entry]));
|
|
719
|
+
for (const assetId of classification.unchangedAssetIds) {
|
|
720
|
+
const record = library.assets[assetId];
|
|
721
|
+
if (!this.#compatibleTypes(record.raw).includes(record.type)) {
|
|
722
|
+
edited.set(assetId, { assetId, path: record.raw, hash: record.rawHash });
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return [...edited.values()].sort((a, b) => compareCodeUnits(a.path, b.path));
|
|
726
|
+
}
|
|
727
|
+
#registerFolderAncestors(library, folder) {
|
|
728
|
+
if (folder === '')
|
|
729
|
+
return;
|
|
730
|
+
const missing = [];
|
|
731
|
+
for (let current = folder; current !== ''; current = parentFolderPath(current)) {
|
|
732
|
+
if (!library.folders.includes(current))
|
|
733
|
+
missing.push(current);
|
|
734
|
+
}
|
|
735
|
+
const keys = new Map(library.folders.map((path) => [portableFolderPathKey(path), path]));
|
|
736
|
+
for (const path of missing.reverse()) {
|
|
737
|
+
const collision = keys.get(portableFolderPathKey(path));
|
|
738
|
+
if (collision !== undefined && collision !== path) {
|
|
739
|
+
throw new Error(`Folder "${path}" collides with existing folder "${collision}".`);
|
|
740
|
+
}
|
|
741
|
+
library.folders.push(path);
|
|
742
|
+
keys.set(portableFolderPathKey(path), path);
|
|
743
|
+
}
|
|
744
|
+
library.folders.sort(compareCodeUnits);
|
|
745
|
+
}
|
|
390
746
|
#requireAsset(library, id) {
|
|
391
747
|
// Own-property check — an inherited key ("constructor", "toString", …)
|
|
392
748
|
// must not pass as an existing asset.
|
|
@@ -20,6 +20,7 @@ import { allDeliveredAssetIds, collectAllReferences, computeLibraryMembership, f
|
|
|
20
20
|
import { jobHash } from '../job-hash.js';
|
|
21
21
|
import { descriptorsByType, processingParametersOf, serverPluginForType, serverPluginsByType } from '../plugins.js';
|
|
22
22
|
import { ensureManagedFolder, listRelativeFiles } from '../folder-filesystem.js';
|
|
23
|
+
import { verifyStableRawFile } from '../raw-reconciliation.js';
|
|
23
24
|
/** Proxy file name shape; its id segment is pinned to core's asset-id grammar. */
|
|
24
25
|
const PROXY_FILE_PATTERN = /^[a-z][A-Za-z0-9]*\.[0-9a-f]{8}\.[a-z0-9]+$/;
|
|
25
26
|
/** Whether a file name in the proxy directory is (shaped like) a fundus proxy. */
|
|
@@ -93,6 +94,7 @@ async function processAssetFile(record, rawPath, proxyPath, config, registry) {
|
|
|
93
94
|
const stem = basename(proxyPath, extension);
|
|
94
95
|
const temporaryPath = join(dirname(proxyPath), `.${stem}.${crypto.randomUUID()}.tmp${extension}`);
|
|
95
96
|
try {
|
|
97
|
+
await verifyStableRawFile(rawPath, record.rawHash);
|
|
96
98
|
const output = await plugin.process({
|
|
97
99
|
sourcePath: rawPath,
|
|
98
100
|
targetPath: temporaryPath,
|
|
@@ -100,6 +102,7 @@ async function processAssetFile(record, rawPath, proxyPath, config, registry) {
|
|
|
100
102
|
preset: plugin.resolvePreset(parameters, config),
|
|
101
103
|
config
|
|
102
104
|
});
|
|
105
|
+
await verifyStableRawFile(rawPath, record.rawHash);
|
|
103
106
|
await rename(temporaryPath, proxyPath);
|
|
104
107
|
return output;
|
|
105
108
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Library } from '../core/index.js';
|
|
2
|
+
export interface ScannedRawFile {
|
|
3
|
+
path: string;
|
|
4
|
+
hash: string;
|
|
5
|
+
}
|
|
6
|
+
export type RawEntryKind = 'file' | 'directory' | 'symlink' | 'special';
|
|
7
|
+
export interface RawDiagnostic {
|
|
8
|
+
/** Empty only for a root-level diagnostic. */
|
|
9
|
+
path: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RawBlocker extends RawDiagnostic {
|
|
13
|
+
assetId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RawScan {
|
|
16
|
+
complete: boolean;
|
|
17
|
+
diagnostics: RawDiagnostic[];
|
|
18
|
+
files: ScannedRawFile[];
|
|
19
|
+
blockers: RawBlocker[];
|
|
20
|
+
/** Every retained entry, including traversal directories and blockers. */
|
|
21
|
+
occupiedPaths: Set<string>;
|
|
22
|
+
entryKinds: Map<string, RawEntryKind>;
|
|
23
|
+
/** Unsupported regular files, retained unhashed only for deletion-safety checks. */
|
|
24
|
+
ignoredRegularPaths: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface EditedRawFile extends ScannedRawFile {
|
|
27
|
+
assetId: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RawMove extends ScannedRawFile {
|
|
30
|
+
assetId: string;
|
|
31
|
+
from: string;
|
|
32
|
+
}
|
|
33
|
+
export interface RawClassification {
|
|
34
|
+
complete: boolean;
|
|
35
|
+
diagnostics: RawDiagnostic[];
|
|
36
|
+
unchangedAssetIds: string[];
|
|
37
|
+
edited: EditedRawFile[];
|
|
38
|
+
moves: RawMove[];
|
|
39
|
+
deletions: Array<{
|
|
40
|
+
assetId: string;
|
|
41
|
+
path: string;
|
|
42
|
+
}>;
|
|
43
|
+
unmanaged: ScannedRawFile[];
|
|
44
|
+
blockers: RawBlocker[];
|
|
45
|
+
}
|
|
46
|
+
/** Hash one regular file without following its final path component. */
|
|
47
|
+
export declare function stableRawFileHash(path: string): Promise<string>;
|
|
48
|
+
export declare function verifyStableRawFile(path: string, expectedHash: string): Promise<void>;
|
|
49
|
+
/** Enumerate without following symlinks and retain structural scan completeness. */
|
|
50
|
+
export declare function scanRawDirectory(rawDir: string, includeFile?: (path: string) => boolean): Promise<RawScan>;
|
|
51
|
+
/** Pure deterministic matching: exact path first, then only 1:1 hash groups. */
|
|
52
|
+
export declare function classifyRawFiles(library: Library, scan: RawScan): RawClassification;
|
|
53
|
+
export declare function classifyRawDirectory(rawDir: string, library: Library, includeExternalFile?: (path: string) => boolean): Promise<RawClassification>;
|