fundus 0.0.4 → 0.2.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 +49 -8
- 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/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.BYvU4SY7.css} +1 -1
- package/build/client/_app/immutable/assets/LibraryContextMenu.BYvU4SY7.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.BYvU4SY7.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/BAEZPv6c.js +1 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAEZPv6c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DVEQFclp.js → ByVSyIj7.js} +1 -1
- package/build/client/_app/immutable/chunks/ByVSyIj7.js.br +1 -0
- package/build/client/_app/immutable/chunks/ByVSyIj7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js +1 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js.br +0 -0
- package/build/client/_app/immutable/chunks/C8W3KwDq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js +1 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.js.br +0 -0
- package/build/client/_app/immutable/chunks/CP-bnOxU.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/Db8LRH6X.js +108 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.js.br +0 -0
- package/build/client/_app/immutable/chunks/Db8LRH6X.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.nM6SgHKJ.js +2 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.br +0 -0
- package/build/client/_app/immutable/entry/app.nM6SgHKJ.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js +1 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DNXvkhtP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js +2 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.QeXQNCp2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js +1 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.dMklHTc2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js +3 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CW4VMcIP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js +1 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CTiY_pbK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js +2 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Dgxx52FJ.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-BUZqFfWG.js → context.js-B77ZAVLi.js} +405 -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-C0rwMMtk.js → exports.js-DvhE7Ffd.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-C0rwMMtk.js.map → exports.js-DvhE7Ffd.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-DvopzwKh.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-DGo2jPcg.js → internal.js-Dn37LrAW.js} +4 -4
- package/build/server/chunks/chunks/{internal.js-DGo2jPcg.js.map → internal.js-Dn37LrAW.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{library-store.js-ZLudCJlT.js → library-store.js-DJ-JRE-g.js} +388 -20
- package/build/server/chunks/chunks/library-store.js-DJ-JRE-g.js.map +1 -0
- package/build/server/chunks/chunks/{shared.js-B1gC9MiJ.js → shared.js-Dob0TlTu.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-B1gC9MiJ.js.map → shared.js-Dob0TlTu.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-DnEJ3UjO.js → utils.js-B4YimMxv.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-DnEJ3UjO.js.map → utils.js-B4YimMxv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Cq5gXVQT.js → _server.ts.js-H4Pq1KAE.js} +4 -5
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Cq5gXVQT.js.map → _server.ts.js-H4Pq1KAE.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-RXbedi8q.js → _server.ts.js-BI3KHSfE.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-RXbedi8q.js.map → _server.ts.js-BI3KHSfE.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-BlNYUbXX.js +34 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/_server.ts.js-BlNYUbXX.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-D7Qae9vU.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-DtO20kgB.js → _server.ts.js-BHKobzAg.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-DtO20kgB.js.map → _server.ts.js-BHKobzAg.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BUebPbd5.js → _server.ts.js-BFKjIOCv.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-BUebPbd5.js.map → _server.ts.js-BFKjIOCv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BjT2qPMT.js → _server.ts.js-BxZeVw8y.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BjT2qPMT.js.map → _server.ts.js-BxZeVw8y.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DYRLsoT7.js → _server.ts.js-CO36U_CT.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DYRLsoT7.js.map → _server.ts.js-CO36U_CT.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BbN2fGNN.js → _server.ts.js-CBlg1Bkl.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-BbN2fGNN.js.map → _server.ts.js-CBlg1Bkl.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-b_MKZ41b.js → _server.ts.js-DcGLCjIs.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-b_MKZ41b.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/state/{_server.ts.js-evEfMpvn.js → _server.ts.js-B1N9o1Ne.js} +5 -6
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-evEfMpvn.js.map → _server.ts.js-B1N9o1Ne.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-C04XE_1w.js → _server.ts.js-B6HbnRqq.js} +4 -5
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-C04XE_1w.js.map → _server.ts.js-B6HbnRqq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DB8TdoEv.js → _server.ts.js-7wzPyG9v.js} +5 -6
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DB8TdoEv.js.map → _server.ts.js-7wzPyG9v.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BrpaijZO.js → _server.ts.js-BWztxYge.js} +5 -6
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-BrpaijZO.js.map → _server.ts.js-BWztxYge.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-BJJwZW_I.js → hooks.server.js-CVKLksJ1.js} +4 -5
- package/build/server/chunks/entries/{hooks.server.js-BJJwZW_I.js.map → hooks.server.js-CVKLksJ1.js.map} +1 -1
- package/build/server/chunks/{handler-DnbS2t1B.js → handler-DZ2Sps4a.js} +5 -5
- package/build/server/chunks/{handler-DnbS2t1B.js.map → handler-DZ2Sps4a.js.map} +1 -1
- package/build/server/chunks/{index.js-g77q3Boj.js → index.js-CdmTWQz8.js} +5 -5
- package/build/server/chunks/{index.js-g77q3Boj.js.map → index.js-CdmTWQz8.js.map} +1 -1
- package/build/server/chunks/{manifest.js-qmEXbstS.js → manifest.js-GhfNr35x.js} +57 -22
- package/build/server/chunks/manifest.js-GhfNr35x.js.map +1 -0
- package/build/server/chunks/nodes/0.js-DGT86_i-.js +11 -0
- package/build/server/chunks/nodes/0.js-DGT86_i-.js.map +1 -0
- package/build/server/chunks/nodes/1.js-DQgYreU0.js +7 -0
- package/build/server/chunks/nodes/{1.js-H-qwo4GJ.js.map → 1.js-DQgYreU0.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-DItreSrX.js +7 -0
- package/build/server/chunks/nodes/2.js-DItreSrX.js.map +1 -0
- package/build/server/chunks/nodes/3.js-bE1dJ_6v.js +7 -0
- package/build/server/chunks/nodes/3.js-bE1dJ_6v.js.map +1 -0
- package/build/server/chunks/nodes/4.js-BebbMVXy.js +7 -0
- package/build/server/chunks/nodes/{4.js-BKL7LSSm.js.map → 4.js-BebbMVXy.js.map} +1 -1
- package/dist/cli.js +32 -9
- package/dist/core/api-types.d.ts +39 -1
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +2 -1
- package/dist/core/ingest.d.ts +2 -0
- package/dist/core/ingest.js +7 -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 +3 -2
- package/dist/server/cli-commands.js +47 -4
- package/dist/server/cli-init.d.ts +1 -1
- package/dist/server/cli-init.js +38 -5
- package/dist/server/http.js +5 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/library-store.d.ts +23 -0
- package/dist/server/library-store.js +82 -14
- package/dist/server/operations.d.ts +12 -2
- package/dist/server/operations.js +391 -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/dist/server/start.d.ts +4 -2
- package/dist/server/start.js +43 -11
- 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/3.JwgtcTEd.css +0 -1
- package/build/client/_app/immutable/assets/3.JwgtcTEd.css.br +0 -0
- package/build/client/_app/immutable/assets/3.JwgtcTEd.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/CNRz3sSw.js.br +0 -0
- package/build/client/_app/immutable/chunks/CNRz3sSw.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CO4RBJfa.js +0 -1
- package/build/client/_app/immutable/chunks/CO4RBJfa.js.br +0 -0
- package/build/client/_app/immutable/chunks/CO4RBJfa.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DOpBpjxS.js +0 -108
- package/build/client/_app/immutable/chunks/DOpBpjxS.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOpBpjxS.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DVEQFclp.js.br +0 -1
- package/build/client/_app/immutable/chunks/DVEQFclp.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DiFoWL2c.js +0 -1
- package/build/client/_app/immutable/chunks/DiFoWL2c.js.br +0 -0
- package/build/client/_app/immutable/chunks/DiFoWL2c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DkADHpZF.js +0 -1
- package/build/client/_app/immutable/chunks/DkADHpZF.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkADHpZF.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CKgtYXQb.js +0 -2
- package/build/client/_app/immutable/entry/app.CKgtYXQb.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CKgtYXQb.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CYZu5qE1.js +0 -1
- package/build/client/_app/immutable/entry/start.CYZu5qE1.js.br +0 -0
- package/build/client/_app/immutable/entry/start.CYZu5qE1.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.I_1SApYQ.js +0 -2
- package/build/client/_app/immutable/nodes/0.I_1SApYQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.I_1SApYQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.v6eUTnWq.js +0 -1
- package/build/client/_app/immutable/nodes/1.v6eUTnWq.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.v6eUTnWq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js +0 -3
- package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DoA7Dfl-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.C-DO70Bs.js +0 -1
- package/build/client/_app/immutable/nodes/3.C-DO70Bs.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.C-DO70Bs.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CALOuSyz.js +0 -2
- package/build/client/_app/immutable/nodes/4.CALOuSyz.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CALOuSyz.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-BUZqFfWG.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-DvopzwKh.js.map +0 -1
- package/build/server/chunks/chunks/library-store.js-ZLudCJlT.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-D7Qae9vU.js.map +0 -1
- package/build/server/chunks/manifest.js-qmEXbstS.js.map +0 -1
- package/build/server/chunks/nodes/0.js-Bk6IZ-vD.js +0 -11
- package/build/server/chunks/nodes/0.js-Bk6IZ-vD.js.map +0 -1
- package/build/server/chunks/nodes/1.js-H-qwo4GJ.js +0 -7
- package/build/server/chunks/nodes/2.js-DRpXVLwp.js +0 -7
- package/build/server/chunks/nodes/2.js-DRpXVLwp.js.map +0 -1
- package/build/server/chunks/nodes/3.js-CDEzNFeU.js +0 -7
- package/build/server/chunks/nodes/3.js-CDEzNFeU.js.map +0 -1
- package/build/server/chunks/nodes/4.js-BKL7LSSm.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,11 +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
|
}
|
|
303
|
+
/** Replace the raw bytes/name and run the normal processing + codegen tail. */
|
|
304
|
+
replaceRawFile(id, options) {
|
|
305
|
+
return this.#enqueueOperation(() => this.#replaceRawFile(id, options));
|
|
306
|
+
}
|
|
307
|
+
async #replaceRawFile(id, options) {
|
|
308
|
+
return this.#afterMutation(await this.#store.replaceRawFile(id, options));
|
|
309
|
+
}
|
|
128
310
|
/** Move a selected asset set as one editor action and roll back on any failure. */
|
|
129
|
-
|
|
311
|
+
moveAssets(assetIds, folder) {
|
|
312
|
+
return this.#enqueueOperation(() => this.#moveAssets(assetIds, folder));
|
|
313
|
+
}
|
|
314
|
+
async #moveAssets(assetIds, folder) {
|
|
130
315
|
const uniqueIds = [...new Set(assetIds)];
|
|
131
316
|
if (uniqueIds.length === 0)
|
|
132
317
|
throw new Error('At least one asset id is required.');
|
|
@@ -154,16 +339,28 @@ export class FundusOperations {
|
|
|
154
339
|
throw cause;
|
|
155
340
|
}
|
|
156
341
|
}
|
|
157
|
-
|
|
342
|
+
createFolder(path) {
|
|
343
|
+
return this.#enqueueOperation(() => this.#createFolder(path));
|
|
344
|
+
}
|
|
345
|
+
async #createFolder(path) {
|
|
158
346
|
return this.#afterMutation(await this.#store.createFolder(path));
|
|
159
347
|
}
|
|
160
|
-
|
|
348
|
+
renameFolder(path, newName) {
|
|
349
|
+
return this.#enqueueOperation(() => this.#renameFolder(path, newName));
|
|
350
|
+
}
|
|
351
|
+
async #renameFolder(path, newName) {
|
|
161
352
|
return this.#afterMutation(await this.#store.renameFolder(path, newName));
|
|
162
353
|
}
|
|
163
|
-
|
|
354
|
+
moveFolder(path, parent) {
|
|
355
|
+
return this.#enqueueOperation(() => this.#moveFolder(path, parent));
|
|
356
|
+
}
|
|
357
|
+
async #moveFolder(path, parent) {
|
|
164
358
|
return this.#afterMutation(await this.#store.moveFolder(path, parent));
|
|
165
359
|
}
|
|
166
|
-
|
|
360
|
+
deleteFolder(path) {
|
|
361
|
+
return this.#enqueueOperation(() => this.#deleteFolder(path));
|
|
362
|
+
}
|
|
363
|
+
async #deleteFolder(path) {
|
|
167
364
|
const result = await this.#store.deleteFolder(path);
|
|
168
365
|
return this.#afterMutation(result.library);
|
|
169
366
|
}
|
|
@@ -175,7 +372,10 @@ export class FundusOperations {
|
|
|
175
372
|
* rejected with nothing written. The REST `patchAsset` keeps the editor's
|
|
176
373
|
* lenient replace semantics.
|
|
177
374
|
*/
|
|
178
|
-
|
|
375
|
+
setAsset(id, changes) {
|
|
376
|
+
return this.#enqueueOperation(() => this.#setAsset(id, changes));
|
|
377
|
+
}
|
|
378
|
+
async #setAsset(id, changes) {
|
|
179
379
|
const library = await this.#store.read();
|
|
180
380
|
this.#requireAsset(library, id);
|
|
181
381
|
return this.#afterMutation(await this.#applyAssetChanges(library, id, changes));
|
|
@@ -185,7 +385,10 @@ export class FundusOperations {
|
|
|
185
385
|
* a referrer keeps pointing at the old id, so renaming a referenced asset
|
|
186
386
|
* introduces dangling-reference errors. Update the referrers first.
|
|
187
387
|
*/
|
|
188
|
-
|
|
388
|
+
renameAsset(id, newId) {
|
|
389
|
+
return this.#enqueueOperation(() => this.#renameAsset(id, newId));
|
|
390
|
+
}
|
|
391
|
+
async #renameAsset(id, newId) {
|
|
189
392
|
const library = await this.#store.read();
|
|
190
393
|
this.#requireAsset(library, id);
|
|
191
394
|
// A collision or invalid new id is the store's own (clearer) error; the
|
|
@@ -204,7 +407,10 @@ export class FundusOperations {
|
|
|
204
407
|
* (the asset is a reference target) is rejected before anything —
|
|
205
408
|
* including the raw file — is removed; the REST route stays lenient.
|
|
206
409
|
*/
|
|
207
|
-
|
|
410
|
+
deleteAsset(id, options) {
|
|
411
|
+
return this.#enqueueOperation(() => this.#deleteAsset(id, options));
|
|
412
|
+
}
|
|
413
|
+
async #deleteAsset(id, options) {
|
|
208
414
|
if (options?.failOnIntroducedIssues) {
|
|
209
415
|
const library = await this.#store.read();
|
|
210
416
|
this.#requireAsset(library, id);
|
|
@@ -215,20 +421,32 @@ export class FundusOperations {
|
|
|
215
421
|
return this.#afterMutation(await this.#store.deleteAsset(id));
|
|
216
422
|
}
|
|
217
423
|
/** Delete a selected asset set as one lenient editor mutation. */
|
|
218
|
-
|
|
424
|
+
deleteAssets(assetIds) {
|
|
425
|
+
return this.#enqueueOperation(() => this.#deleteAssets(assetIds));
|
|
426
|
+
}
|
|
427
|
+
async #deleteAssets(assetIds) {
|
|
219
428
|
const uniqueIds = [...new Set(assetIds)];
|
|
220
429
|
if (uniqueIds.length === 0)
|
|
221
430
|
throw new Error('At least one asset id is required.');
|
|
222
431
|
return this.#afterMutation(await this.#store.deleteAssets(uniqueIds));
|
|
223
432
|
}
|
|
224
|
-
|
|
433
|
+
createManifest(name) {
|
|
434
|
+
return this.#enqueueOperation(() => this.#createManifest(name));
|
|
435
|
+
}
|
|
436
|
+
async #createManifest(name) {
|
|
225
437
|
return this.#afterMutation(await this.#store.createManifest(name));
|
|
226
438
|
}
|
|
227
|
-
|
|
439
|
+
setManifestAppearance(name, appearance) {
|
|
440
|
+
return this.#enqueueOperation(() => this.#setManifestAppearance(name, appearance));
|
|
441
|
+
}
|
|
442
|
+
async #setManifestAppearance(name, appearance) {
|
|
228
443
|
return this.#afterMutation(await this.#store.setManifestAppearance(name, appearance));
|
|
229
444
|
}
|
|
230
445
|
/** Apply a PATCH body: appearance fields and/or a rename — in that order. */
|
|
231
|
-
|
|
446
|
+
patchManifest(name, body) {
|
|
447
|
+
return this.#enqueueOperation(() => this.#patchManifest(name, body));
|
|
448
|
+
}
|
|
449
|
+
async #patchManifest(name, body) {
|
|
232
450
|
let library = await this.#store.setManifestAppearance(name, {
|
|
233
451
|
color: body.color,
|
|
234
452
|
emoji: body.emoji
|
|
@@ -247,17 +465,33 @@ export class FundusOperations {
|
|
|
247
465
|
* the module must be updated by hand — both the file name and the exported
|
|
248
466
|
* const change with the manifest name.
|
|
249
467
|
*/
|
|
250
|
-
|
|
468
|
+
renameManifest(name, newName) {
|
|
469
|
+
return this.#enqueueOperation(() => this.#renameManifest(name, newName));
|
|
470
|
+
}
|
|
471
|
+
async #renameManifest(name, newName) {
|
|
251
472
|
return this.#afterMutation(await this.#store.renameManifest(name, newName));
|
|
252
473
|
}
|
|
253
|
-
|
|
474
|
+
deleteManifest(name) {
|
|
475
|
+
return this.#enqueueOperation(() => this.#deleteManifest(name));
|
|
476
|
+
}
|
|
477
|
+
async #deleteManifest(name) {
|
|
254
478
|
return this.#afterMutation(await this.#store.deleteManifest(name));
|
|
255
479
|
}
|
|
256
480
|
/** The autosave chain tail: process + regenerate, then report fresh state. */
|
|
257
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) {
|
|
258
490
|
const pipelineIssues = [];
|
|
259
|
-
const
|
|
260
|
-
|
|
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) {
|
|
261
495
|
try {
|
|
262
496
|
const report = await processAssets(this.#config, library, this.#registry);
|
|
263
497
|
await generateManifestModules(this.#config, library, report.proxies, this.#registry);
|
|
@@ -268,13 +502,15 @@ export class FundusOperations {
|
|
|
268
502
|
});
|
|
269
503
|
}
|
|
270
504
|
}
|
|
271
|
-
|
|
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
|
+
};
|
|
272
512
|
}
|
|
273
|
-
async #buildState(library,
|
|
274
|
-
const issues = [
|
|
275
|
-
...(precomputedIssues ?? (await this.#store.validate(library))),
|
|
276
|
-
...extraIssues
|
|
277
|
-
];
|
|
513
|
+
async #buildState(library, issues, classification) {
|
|
278
514
|
const referencesByAsset = collectAllReferences(library, descriptorsByType(this.#registry));
|
|
279
515
|
const membershipByManifest = computeLibraryMembership(library, referencesByAsset);
|
|
280
516
|
const delivered = allDeliveredAssetIds(membershipByManifest);
|
|
@@ -334,7 +570,7 @@ export class FundusOperations {
|
|
|
334
570
|
type: record.type,
|
|
335
571
|
raw: record.raw,
|
|
336
572
|
folder: folderOfRawPath(record.raw),
|
|
337
|
-
rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}`,
|
|
573
|
+
rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}?v=${record.rawHash.slice(0, 12)}`,
|
|
338
574
|
rawHash: record.rawHash,
|
|
339
575
|
parameters: record.parameters,
|
|
340
576
|
manifests: record.manifests,
|
|
@@ -374,15 +610,139 @@ export class FundusOperations {
|
|
|
374
610
|
for (const [type, plugin] of Object.entries(this.#registry)) {
|
|
375
611
|
presetNames[type] = Object.keys(plugin.presets(this.#config)).sort();
|
|
376
612
|
}
|
|
613
|
+
const rawIngestionQueue = this.#buildRawIngestionQueue(library, classification);
|
|
377
614
|
return {
|
|
378
615
|
library,
|
|
379
616
|
assets,
|
|
380
617
|
issues,
|
|
381
618
|
presetNames,
|
|
382
619
|
manifestMemberships,
|
|
383
|
-
manifestDeliveredBytes
|
|
620
|
+
manifestDeliveredBytes,
|
|
621
|
+
rawIngestionQueue
|
|
384
622
|
};
|
|
385
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
|
+
}
|
|
386
746
|
#requireAsset(library, id) {
|
|
387
747
|
// Own-property check — an inherited key ("constructor", "toString", …)
|
|
388
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>;
|