fundus 0.0.2 → 0.0.3
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 +58 -0
- 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.wavcNhPC.css +1 -0
- package/build/client/_app/immutable/assets/2.wavcNhPC.css.br +0 -0
- package/build/client/_app/immutable/assets/2.wavcNhPC.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.Cjh_fXPY.css +1 -0
- package/build/client/_app/immutable/assets/3.Cjh_fXPY.css.br +0 -0
- package/build/client/_app/immutable/assets/3.Cjh_fXPY.css.gz +0 -0
- package/build/client/_app/immutable/assets/4.p004hmEN.css +1 -0
- package/build/client/_app/immutable/assets/4.p004hmEN.css.br +0 -0
- package/build/client/_app/immutable/assets/4.p004hmEN.css.gz +0 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css +1 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.br +0 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.zyZtpKzC.css +1 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.zyZtpKzC.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.zyZtpKzC.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BiIto6ft.js +1 -0
- package/build/client/_app/immutable/chunks/BiIto6ft.js.br +0 -0
- package/build/client/_app/immutable/chunks/BiIto6ft.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.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/CO4RBJfa.js.br +0 -0
- package/build/client/_app/immutable/chunks/CO4RBJfa.js.gz +0 -0
- package/{dist/editor/_app/immutable/chunks/DjvOiDmq.js → build/client/_app/immutable/chunks/CvY6YsFE.js} +1 -1
- package/build/client/_app/immutable/chunks/CvY6YsFE.js.br +0 -0
- package/build/client/_app/immutable/chunks/CvY6YsFE.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DL0_39Ys.js +5 -0
- package/build/client/_app/immutable/chunks/DL0_39Ys.js.br +0 -0
- package/build/client/_app/immutable/chunks/DL0_39Ys.js.gz +0 -0
- package/{dist/editor/_app/immutable/chunks/BfPE5wdt.js → build/client/_app/immutable/chunks/DQ8SXFaX.js} +1 -1
- package/build/client/_app/immutable/chunks/DQ8SXFaX.js.br +0 -0
- package/build/client/_app/immutable/chunks/DQ8SXFaX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DjdrdRzT.js.br +0 -0
- package/build/client/_app/immutable/chunks/DjdrdRzT.js.gz +0 -0
- package/build/client/_app/immutable/chunks/xihTtKlq.js.br +1 -0
- package/build/client/_app/immutable/chunks/xihTtKlq.js.gz +0 -0
- package/{dist/editor/_app/immutable/entry/app.BkqbfI8n.js → build/client/_app/immutable/entry/app.ubN0v9bN.js} +2 -2
- package/build/client/_app/immutable/entry/app.ubN0v9bN.js.br +0 -0
- package/build/client/_app/immutable/entry/app.ubN0v9bN.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BW8dBDh6.js +1 -0
- package/build/client/_app/immutable/entry/start.BW8dBDh6.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BW8dBDh6.js.gz +0 -0
- package/{dist/editor/_app/immutable/nodes/0.xdMI5GEU.js → build/client/_app/immutable/nodes/0.Dr5ijCIl.js} +1 -1
- package/build/client/_app/immutable/nodes/0.Dr5ijCIl.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Dr5ijCIl.js.gz +0 -0
- package/{dist/editor/_app/immutable/nodes/1.D9p2tfoJ.js → build/client/_app/immutable/nodes/1.DiIGIRAb.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DiIGIRAb.js.br +3 -0
- package/build/client/_app/immutable/nodes/1.DiIGIRAb.js.gz +0 -0
- package/{dist/editor/_app/immutable/nodes/2.BrA0Hyzu.js → build/client/_app/immutable/nodes/2.B8KYmQcV.js} +3 -3
- package/build/client/_app/immutable/nodes/2.B8KYmQcV.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.B8KYmQcV.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.B1jc7arT.js +104 -0
- package/build/client/_app/immutable/nodes/3.B1jc7arT.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.B1jc7arT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.BPK9n1mH.js +2 -0
- package/build/client/_app/immutable/nodes/4.BPK9n1mH.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.BPK9n1mH.js.gz +0 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/env.js +100 -0
- package/build/env.js.map +1 -0
- package/build/handler.js +22 -0
- package/build/handler.js.map +1 -0
- package/build/index.js +145 -0
- package/build/index.js.map +1 -0
- package/build/server/chunks/chunks/context.js-Bw4WWLhG.js +443 -0
- package/build/server/chunks/chunks/context.js-Bw4WWLhG.js.map +1 -0
- package/build/server/chunks/chunks/core.js-DDntpb72.js +409 -0
- package/build/server/chunks/chunks/core.js-DDntpb72.js.map +1 -0
- package/build/server/chunks/chunks/env.js-l0sNRNKZ.js +2 -0
- package/build/server/chunks/chunks/env.js-l0sNRNKZ.js.map +1 -0
- package/build/server/chunks/chunks/exports.js-CLTzaOxn.js +152 -0
- package/build/server/chunks/chunks/exports.js-CLTzaOxn.js.map +1 -0
- package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js +64 -0
- package/build/server/chunks/chunks/folder-path.js-ytx9ucgg.js.map +1 -0
- package/build/server/chunks/chunks/http.js-Dxoa-jtP.js +100 -0
- package/build/server/chunks/chunks/http.js-Dxoa-jtP.js.map +1 -0
- package/build/server/chunks/chunks/internal.js-BDBSzJId.js +5753 -0
- package/build/server/chunks/chunks/internal.js-BDBSzJId.js.map +1 -0
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js +22 -0
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -0
- package/{dist/operations-DYM-5KVl.js → build/server/chunks/chunks/library-store.js-BP8cL-qD.js} +344 -444
- package/build/server/chunks/chunks/library-store.js-BP8cL-qD.js.map +1 -0
- package/build/server/chunks/chunks/manifest-appearance.js-DTk355LQ.js +146 -0
- package/build/server/chunks/chunks/manifest-appearance.js-DTk355LQ.js.map +1 -0
- package/build/server/chunks/chunks/routing.js-y-tryuip.js +213 -0
- package/build/server/chunks/chunks/routing.js-y-tryuip.js.map +1 -0
- package/build/server/chunks/chunks/shared.js-BWXgl1Xf.js +255 -0
- package/build/server/chunks/chunks/shared.js-BWXgl1Xf.js.map +1 -0
- package/build/server/chunks/chunks/utils.js-9oI5Oz0w.js +1048 -0
- package/build/server/chunks/chunks/utils.js-9oI5Oz0w.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/_...path_/_server.ts.js-WknZVW_S.js +27 -0
- package/build/server/chunks/entries/endpoints/api/_...path_/_server.ts.js-WknZVW_S.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/_server.ts.js-BrZ__-HZ.js +24 -0
- package/build/server/chunks/entries/endpoints/api/assets/_id_/_server.ts.js-BrZ__-HZ.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-PqADGPXc.js +45 -0
- package/build/server/chunks/entries/endpoints/api/assets/_server.ts.js-PqADGPXc.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/delete/_server.ts.js-OqoE5PN-.js +31 -0
- package/build/server/chunks/entries/endpoints/api/assets/delete/_server.ts.js-OqoE5PN-.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/assets/move/_server.ts.js-CJ08e1ho.js +31 -0
- package/build/server/chunks/entries/endpoints/api/assets/move/_server.ts.js-CJ08e1ho.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/folders/_path_/_server.ts.js-DdF3eK2I.js +38 -0
- package/build/server/chunks/entries/endpoints/api/folders/_path_/_server.ts.js-DdF3eK2I.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/folders/_server.ts.js-Cxt6jsJ5.js +31 -0
- package/build/server/chunks/entries/endpoints/api/folders/_server.ts.js-Cxt6jsJ5.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/_server.ts.js-BWVM2Ntl.js +24 -0
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/_server.ts.js-BWVM2Ntl.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/manifests/_server.ts.js-Z2bM-qHM.js +31 -0
- package/build/server/chunks/entries/endpoints/api/manifests/_server.ts.js-Z2bM-qHM.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/state/_server.ts.js-BX9H2Z34.js +24 -0
- package/build/server/chunks/entries/endpoints/api/state/_server.ts.js-BX9H2Z34.js.map +1 -0
- package/build/server/chunks/entries/endpoints/files/_...path_/_server.ts.js-BWHDZ8mz.js +27 -0
- package/build/server/chunks/entries/endpoints/files/_...path_/_server.ts.js-BWHDZ8mz.js.map +1 -0
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/_server.ts.js-CR_7mndO.js +24 -0
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/_server.ts.js-CR_7mndO.js.map +1 -0
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/_server.ts.js-Cw1PDBvb.js +24 -0
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/_server.ts.js-Cw1PDBvb.js.map +1 -0
- package/build/server/chunks/entries/hooks.server.js-BHoE0yBi.js +23 -0
- package/build/server/chunks/entries/hooks.server.js-BHoE0yBi.js.map +1 -0
- package/build/server/chunks/handler-10cx4zHn.js +1475 -0
- package/build/server/chunks/handler-10cx4zHn.js.map +1 -0
- package/build/server/chunks/index.js-DvPfiC00.js +3789 -0
- package/build/server/chunks/index.js-DvPfiC00.js.map +1 -0
- package/build/server/chunks/manifest.js-DoPjUnzD.js +149 -0
- package/build/server/chunks/manifest.js-DoPjUnzD.js.map +1 -0
- package/build/server/chunks/nodes/0.js-Di3qgAtW.js +11 -0
- package/build/server/chunks/nodes/0.js-Di3qgAtW.js.map +1 -0
- package/build/server/chunks/nodes/1.js-BlWSFIA8.js +7 -0
- package/build/server/chunks/nodes/1.js-BlWSFIA8.js.map +1 -0
- package/build/server/chunks/nodes/2.js-CO9nSPUN.js +7 -0
- package/build/server/chunks/nodes/2.js-CO9nSPUN.js.map +1 -0
- package/build/server/chunks/nodes/3.js-C2D6xpEO.js +7 -0
- package/build/server/chunks/nodes/3.js-C2D6xpEO.js.map +1 -0
- package/build/server/chunks/nodes/4.js-B3IuagJW.js +7 -0
- package/build/server/chunks/nodes/4.js-B3IuagJW.js.map +1 -0
- package/build/shims.js +33 -0
- package/build/shims.js.map +1 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +109 -1332
- package/dist/config.d.ts +3 -2
- package/dist/config.js +2 -2
- package/dist/core/api-types.d.ts +141 -0
- package/dist/core/api-types.js +1 -0
- package/dist/core/asset-id.d.ts +28 -0
- package/dist/core/asset-id.js +59 -0
- package/dist/core/asset-reference.d.ts +23 -0
- package/dist/core/asset-reference.js +15 -0
- package/dist/core/canonicalize.d.ts +9 -0
- package/dist/core/canonicalize.js +36 -0
- package/dist/core/chroma-key.d.ts +30 -0
- package/dist/core/chroma-key.js +55 -0
- package/dist/core/config.d.ts +35 -0
- package/dist/core/config.js +4 -0
- package/dist/core/folder-path.d.ts +19 -0
- package/dist/core/folder-path.js +86 -0
- package/dist/core/index.d.ts +33 -499
- package/dist/core/index.js +17 -962
- package/dist/core/ingest.d.ts +18 -0
- package/dist/core/ingest.js +58 -0
- package/dist/core/library.d.ts +54 -0
- package/dist/core/library.js +138 -0
- package/dist/core/manifest-appearance.d.ts +35 -0
- package/dist/core/manifest-appearance.js +123 -0
- package/dist/core/manifest-entry.d.ts +104 -0
- package/dist/core/manifest-entry.js +1 -0
- package/dist/core/manifest-membership.d.ts +56 -0
- package/dist/core/manifest-membership.js +54 -0
- package/dist/core/plugins/audio.d.ts +26 -0
- package/dist/core/plugins/audio.js +44 -0
- package/dist/core/plugins/chroma-key-video.d.ts +29 -0
- package/dist/core/plugins/chroma-key-video.js +69 -0
- package/dist/core/plugins/contract.d.ts +35 -0
- package/dist/core/plugins/contract.js +1 -0
- package/dist/core/plugins/image.d.ts +30 -0
- package/dist/core/plugins/image.js +45 -0
- package/dist/core/plugins/slice.d.ts +22 -0
- package/dist/core/plugins/slice.js +60 -0
- package/dist/core/plugins/video.d.ts +45 -0
- package/dist/core/plugins/video.js +77 -0
- package/dist/core/reference-graph.d.ts +41 -0
- package/dist/core/reference-graph.js +138 -0
- package/dist/core/slice-data.d.ts +61 -0
- package/dist/core/slice-data.js +49 -0
- package/dist/runtime/chroma-key-gl.d.ts +40 -22
- package/dist/runtime/chroma-key-gl.js +189 -183
- package/dist/runtime/components/ChromaKeyVideo.svelte +3 -3
- package/dist/runtime/components/ChromaKeyVideo.svelte.d.ts +21 -0
- package/dist/runtime/components/Image.svelte +1 -1
- package/dist/runtime/components/Image.svelte.d.ts +9 -0
- package/dist/runtime/components/Slice.svelte +1 -1
- package/dist/runtime/components/Slice.svelte.d.ts +19 -0
- package/dist/runtime/components/SliceCanvas.svelte +3 -3
- package/dist/runtime/components/SliceCanvas.svelte.d.ts +10 -0
- package/dist/runtime/components/SliceDom.svelte +3 -3
- package/dist/runtime/components/SliceDom.svelte.d.ts +10 -0
- package/dist/runtime/components/Video.svelte +2 -2
- package/dist/runtime/components/Video.svelte.d.ts +18 -0
- package/dist/runtime/geometry.d.ts +55 -35
- package/dist/runtime/geometry.js +186 -191
- package/dist/runtime/index.d.ts +9 -9
- package/dist/runtime/index.js +9 -9
- package/dist/runtime/kind-handlers.d.ts +33 -24
- package/dist/runtime/kind-handlers.js +74 -65
- package/dist/runtime/manifest.d.ts +53 -47
- package/dist/runtime/manifest.js +133 -113
- package/dist/runtime/observe-border-box-size.d.ts +1 -4
- package/dist/runtime/observe-border-box-size.js +9 -10
- package/dist/runtime/preload-registry.d.ts +22 -8
- package/dist/runtime/preload-registry.js +65 -62
- package/dist/server/build.d.ts +8 -0
- package/dist/server/build.js +25 -0
- package/dist/server/check.d.ts +24 -0
- package/dist/server/check.js +199 -0
- package/dist/server/cli-commands.d.ts +25 -0
- package/dist/server/cli-commands.js +1178 -0
- package/dist/server/cli-init.d.ts +3 -0
- package/dist/server/cli-init.js +53 -0
- package/dist/server/cli-io.d.ts +46 -0
- package/dist/server/cli-io.js +105 -0
- package/dist/server/codegen.d.ts +58 -0
- package/dist/server/codegen.js +185 -0
- package/dist/server/config-loader.d.ts +34 -0
- package/dist/server/config-loader.js +103 -0
- package/dist/server/context.d.ts +8 -0
- package/dist/server/context.js +19 -0
- package/dist/server/folder-filesystem.d.ts +7 -0
- package/dist/server/folder-filesystem.js +39 -0
- package/dist/server/http.d.ts +12 -0
- package/dist/server/http.js +100 -0
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.js +9 -0
- package/dist/server/job-hash.d.ts +10 -0
- package/dist/server/job-hash.js +23 -0
- package/dist/server/library-store.d.ts +82 -0
- package/dist/server/library-store.js +763 -0
- package/dist/server/operations.d.ts +87 -0
- package/dist/server/operations.js +427 -0
- package/dist/server/plugins/audio.d.ts +7 -0
- package/dist/server/plugins/audio.js +27 -0
- package/dist/server/plugins/chroma-key-video.d.ts +9 -0
- package/dist/server/plugins/chroma-key-video.js +38 -0
- package/dist/server/plugins/contract.d.ts +100 -0
- package/dist/server/plugins/contract.js +16 -0
- package/dist/server/plugins/image.d.ts +8 -0
- package/dist/server/plugins/image.js +30 -0
- package/dist/server/plugins/slice.d.ts +14 -0
- package/dist/server/plugins/slice.js +63 -0
- package/dist/server/plugins/video.d.ts +6 -0
- package/dist/server/plugins/video.js +29 -0
- package/dist/server/plugins.d.ts +23 -0
- package/dist/server/plugins.js +33 -0
- package/dist/server/processing/audio-processing.d.ts +17 -0
- package/dist/server/processing/audio-processing.js +46 -0
- package/dist/server/processing/ffmpeg.d.ts +9 -0
- package/dist/server/processing/ffmpeg.js +50 -0
- package/dist/server/processing/image-processing.d.ts +16 -0
- package/dist/server/processing/image-processing.js +26 -0
- package/dist/server/processing/media-probe.d.ts +15 -0
- package/dist/server/processing/media-probe.js +27 -0
- package/dist/server/processing/processor.d.ts +50 -0
- package/dist/server/processing/processor.js +158 -0
- package/dist/server/processing/video-processing.d.ts +35 -0
- package/dist/server/processing/video-processing.js +87 -0
- package/dist/server/server.d.ts +13 -0
- package/dist/server/server.js +57 -0
- package/dist/server/start.d.ts +1 -0
- package/dist/server/start.js +33 -0
- package/package.json +29 -16
- package/dist/build-CZYFLLvl.js +0 -191
- package/dist/core/image-Dqmz8FXS.js +0 -54
- package/dist/core/reference-graph-CjuHbLDw.d.ts +0 -280
- package/dist/core/testing.d.ts +0 -27
- package/dist/core/testing.js +0 -80
- package/dist/editor/200.html +0 -37
- package/dist/editor/_app/immutable/assets/2.DIbbW7U1.css +0 -1
- package/dist/editor/_app/immutable/assets/3.C-9vFQ9A.css +0 -1
- package/dist/editor/_app/immutable/assets/4.BS8ElpOV.css +0 -1
- package/dist/editor/_app/immutable/assets/ConfirmDialog.CHEISE-L.css +0 -1
- package/dist/editor/_app/immutable/assets/LibraryContextMenu.CDjCmdV-.css +0 -1
- package/dist/editor/_app/immutable/chunks/Cx6qtxJg.js +0 -1
- package/dist/editor/_app/immutable/chunks/n7mvYc42.js +0 -5
- package/dist/editor/_app/immutable/entry/start.C8HHOLly.js +0 -1
- package/dist/editor/_app/immutable/nodes/3.WO7tZH_d.js +0 -104
- package/dist/editor/_app/immutable/nodes/4.DQtx17aW.js +0 -2
- package/dist/editor/_app/version.json +0 -1
- package/dist/index.d.ts +0 -370
- package/dist/index.js +0 -4
- package/dist/start-Bhnqt9Dc.js +0 -279
- package/dist/start-D2IXOrni.js +0 -2
- /package/{dist/editor → build/client}/_app/immutable/assets/0.DLhIsYA_.css +0 -0
- /package/{dist/editor → build/client}/_app/immutable/chunks/Bjy-W4x2.js +0 -0
- /package/{dist/editor → build/client}/_app/immutable/chunks/CNRz3sSw.js +0 -0
- /package/{dist/editor → build/client}/_app/immutable/chunks/CO4RBJfa.js +0 -0
- /package/{dist/editor → build/client}/_app/immutable/chunks/DjdrdRzT.js +0 -0
- /package/{dist/editor → build/client}/_app/immutable/chunks/xihTtKlq.js +0 -0
package/{dist/operations-DYM-5KVl.js → build/server/chunks/chunks/library-store.js-BP8cL-qD.js}
RENAMED
|
@@ -1,87 +1,330 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { n as normalizeFolderPath, f as folderOfRawPath, r as rawFileNameOfPath, a as folderPathError, p as parentFolderPath, b as folderNameError, j as joinFolderPath, c as folderNameOf, i as isFolderOrDescendant, d as replaceFolderPrefix, e as portableFolderPathKey, F as FOLDER_SENTINEL } from './folder-path.js-ytx9ucgg.js';
|
|
2
|
+
import { a as audioPlugin, c as chromaKeyVideoPlugin, v as videoPlugin, s as slicePlugin, i as imagePlugin, d as defaultInsetsForSize, b as assetIdError, m as manifestNameError, e as isAssetReference } from './core.js-DDntpb72.js';
|
|
3
|
+
import { m as manifestColorError, a as manifestEmojiError } from './manifest-appearance.js-DTk355LQ.js';
|
|
4
|
+
import { join, extname, dirname, basename } from 'node:path';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { readFile, mkdir, writeFile, rename, rm, unlink, stat, readdir } from 'node:fs/promises';
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import ffmpegStaticPath from 'ffmpeg-static';
|
|
9
|
+
import { path } from 'ffprobe-static';
|
|
10
|
+
import { execFile } from 'node:child_process';
|
|
11
|
+
import { promisify } from 'node:util';
|
|
12
|
+
import sharp from 'sharp';
|
|
13
|
+
|
|
14
|
+
//#region src/lib/core/library.ts
|
|
15
|
+
/** A fresh, empty library. */
|
|
16
|
+
function createEmptyLibrary() {
|
|
17
|
+
return {
|
|
18
|
+
version: 1,
|
|
19
|
+
folders: [],
|
|
20
|
+
assets: {},
|
|
21
|
+
manifests: {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
18
24
|
function isRecord(value) {
|
|
19
25
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
26
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Keys that are prototype-pollution hazards as record keys: assigning
|
|
29
|
+
* `assets["__proto__"] = …` on a plain object replaces its prototype instead
|
|
30
|
+
* of adding an entry. Rejected outright at parse time — id *validity* beyond
|
|
31
|
+
* this is a validator concern (reported as issues, not parse failures).
|
|
32
|
+
*/
|
|
33
|
+
var DANGEROUS_KEYS = [
|
|
34
|
+
"__proto__",
|
|
35
|
+
"constructor",
|
|
36
|
+
"prototype"
|
|
37
|
+
];
|
|
38
|
+
function assertSafeKey(kind, key) {
|
|
39
|
+
if (DANGEROUS_KEYS.includes(key)) throw new Error(`Library ${kind} key "${key}" is not allowed.`);
|
|
25
40
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
if (!
|
|
33
|
-
if (pluginConfig.presets === void 0) return merged;
|
|
34
|
-
if (!isRecord(pluginConfig.presets)) throw new Error(`fundus.config: "plugins.${label}.presets" must be an object.`);
|
|
35
|
-
for (const [name, preset] of Object.entries(pluginConfig.presets)) merged[name] = validatePreset(name, preset);
|
|
36
|
-
return merged;
|
|
37
|
-
}
|
|
38
|
-
/** `plugins.video.ffmpegPath` / `ffprobePath`: optional system-binary overrides. */
|
|
39
|
-
function binaryPathOverride(projectRoot, pluginConfig, key) {
|
|
40
|
-
if (!isRecord(pluginConfig) || pluginConfig[key] === void 0) return null;
|
|
41
|
-
const value = pluginConfig[key];
|
|
42
|
-
if (typeof value !== "string" || value.length === 0) throw new Error(`fundus.config: "plugins.video.${key}" must be a non-empty string.`);
|
|
43
|
-
return resolvePath(projectRoot, value);
|
|
44
|
-
}
|
|
45
|
-
/** Pure resolution step (testable without a config file on disk). */
|
|
46
|
-
function resolveConfig(projectRoot, value) {
|
|
47
|
-
if (!isRecord(value)) throw new Error("fundus.config must default-export a configuration object.");
|
|
48
|
-
const proxyBasePath = requiredString(value, "proxyBasePath");
|
|
49
|
-
if (!proxyBasePath.endsWith("/")) throw new Error("fundus.config: \"proxyBasePath\" must end with \"/\".");
|
|
50
|
-
const plugins = value.plugins === void 0 ? {} : value.plugins;
|
|
51
|
-
if (!isRecord(plugins)) throw new Error("fundus.config: \"plugins\" must be an object.");
|
|
41
|
+
function parseAssetRecord(id, value) {
|
|
42
|
+
if (!isRecord(value)) throw new Error(`Library asset "${id}" must be an object.`);
|
|
43
|
+
if (typeof value.type !== "string" || value.type.length === 0) throw new Error(`Library asset "${id}" is missing a string "type".`);
|
|
44
|
+
if (typeof value.raw !== "string" || value.raw.length === 0) throw new Error(`Library asset "${id}" is missing a string "raw" file name.`);
|
|
45
|
+
if (typeof value.rawHash !== "string" || value.rawHash.length === 0) throw new Error(`Library asset "${id}" is missing a string "rawHash".`);
|
|
46
|
+
if (!("parameters" in value)) throw new Error(`Library asset "${id}" is missing "parameters".`);
|
|
47
|
+
if (!Array.isArray(value.manifests) || value.manifests.some((entry) => typeof entry !== "string")) throw new Error(`Library asset "${id}" must have a "manifests" array of strings.`);
|
|
52
48
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
plugins: {
|
|
59
|
-
image: { presets: mergePresets("image", IMAGE_BUILT_IN_PRESETS, plugins.image, validateImagePreset) },
|
|
60
|
-
slice: { presets: mergePresets("slice", SLICE_BUILT_IN_PRESETS, plugins.slice, validateImagePreset) },
|
|
61
|
-
video: {
|
|
62
|
-
presets: mergePresets("video", VIDEO_BUILT_IN_PRESETS, plugins.video, validateVideoPreset),
|
|
63
|
-
ffmpegPath: binaryPathOverride(projectRoot, plugins.video, "ffmpegPath"),
|
|
64
|
-
ffprobePath: binaryPathOverride(projectRoot, plugins.video, "ffprobePath")
|
|
65
|
-
},
|
|
66
|
-
audio: { presets: mergePresets("audio", AUDIO_BUILT_IN_PRESETS, plugins.audio, validateAudioPreset) }
|
|
67
|
-
}
|
|
49
|
+
type: value.type,
|
|
50
|
+
raw: value.raw,
|
|
51
|
+
rawHash: value.rawHash,
|
|
52
|
+
parameters: value.parameters,
|
|
53
|
+
manifests: value.manifests
|
|
68
54
|
};
|
|
69
55
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
56
|
+
function parseManifestRecord(name, value) {
|
|
57
|
+
const record = {};
|
|
58
|
+
if ("color" in value && value.color !== void 0) {
|
|
59
|
+
if (typeof value.color !== "string") throw new Error(`Library manifest "${name}" has a non-string "color".`);
|
|
60
|
+
record.color = value.color;
|
|
61
|
+
}
|
|
62
|
+
if ("emoji" in value && value.emoji !== void 0) {
|
|
63
|
+
if (typeof value.emoji !== "string") throw new Error(`Library manifest "${name}" has a non-string "emoji".`);
|
|
64
|
+
record.emoji = value.emoji;
|
|
65
|
+
}
|
|
66
|
+
return record;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse and structurally validate a library file's JSON text. Throws with a
|
|
70
|
+
* clear message on any malformed part; plugin parameter validation is a
|
|
71
|
+
* separate, plugin-owned step.
|
|
72
|
+
*/
|
|
73
|
+
function parseLibrary(json) {
|
|
74
|
+
let value;
|
|
76
75
|
try {
|
|
77
|
-
|
|
76
|
+
value = JSON.parse(json);
|
|
78
77
|
} catch (cause) {
|
|
79
|
-
throw new Error(`
|
|
78
|
+
throw new Error(`Library file is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(value)) throw new Error("Library file must contain a JSON object.");
|
|
81
|
+
if (value.version !== 1) throw new Error(`Unsupported library version ${JSON.stringify(value.version)} (expected 1).`);
|
|
82
|
+
if (!isRecord(value.assets)) throw new Error("Library \"assets\" must be an object.");
|
|
83
|
+
if (!isRecord(value.manifests)) throw new Error("Library \"manifests\" must be an object.");
|
|
84
|
+
if (value.folders !== void 0 && (!Array.isArray(value.folders) || value.folders.some((entry) => typeof entry !== "string"))) throw new Error("Library \"folders\" must be an array of strings.");
|
|
85
|
+
const folders = value.folders === void 0 ? [] : value.folders;
|
|
86
|
+
for (const folder of folders) {
|
|
87
|
+
const problem = folderPathError(folder);
|
|
88
|
+
if (problem) throw new Error(`Invalid library folder: ${problem}`);
|
|
89
|
+
}
|
|
90
|
+
const assets = {};
|
|
91
|
+
for (const [id, record] of Object.entries(value.assets)) {
|
|
92
|
+
assertSafeKey("asset", id);
|
|
93
|
+
assets[id] = parseAssetRecord(id, record);
|
|
94
|
+
}
|
|
95
|
+
const manifests = {};
|
|
96
|
+
for (const [name, record] of Object.entries(value.manifests)) {
|
|
97
|
+
assertSafeKey("manifest", name);
|
|
98
|
+
if (!isRecord(record)) throw new Error(`Library manifest "${name}" must be an object.`);
|
|
99
|
+
manifests[name] = parseManifestRecord(name, record);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
version: 1,
|
|
103
|
+
folders,
|
|
104
|
+
assets,
|
|
105
|
+
manifests
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Recursively sort object keys so serialization is order-independent. */
|
|
109
|
+
function sortValue(value) {
|
|
110
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
111
|
+
if (typeof value === "object" && value !== null) {
|
|
112
|
+
const sorted = {};
|
|
113
|
+
for (const key of Object.keys(value).sort()) {
|
|
114
|
+
const entry = value[key];
|
|
115
|
+
if (entry !== void 0) sorted[key] = sortValue(entry);
|
|
116
|
+
}
|
|
117
|
+
return sorted;
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Serialize a library deterministically: recursively sorted keys, tab-indented,
|
|
123
|
+
* trailing newline. Same library → byte-identical file, whatever the in-memory
|
|
124
|
+
* insertion order was.
|
|
125
|
+
*/
|
|
126
|
+
function serializeLibrary(library) {
|
|
127
|
+
return `${JSON.stringify(sortValue({
|
|
128
|
+
...library,
|
|
129
|
+
folders: [...library.folders].sort()
|
|
130
|
+
}), null, " ")}\n`;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/lib/core/canonicalize.ts
|
|
134
|
+
/**
|
|
135
|
+
* Stable JSON for hash inputs: recursively sorted keys, no whitespace,
|
|
136
|
+
* `undefined` object values dropped. Two parameter objects that differ only in
|
|
137
|
+
* key order (or in explicitly-undefined optionals) canonicalize identically,
|
|
138
|
+
* so the derived job hash — and thus the proxy file name — stays stable.
|
|
139
|
+
*/
|
|
140
|
+
function canonicalizeParameters(parameters) {
|
|
141
|
+
return stringify(parameters);
|
|
142
|
+
}
|
|
143
|
+
/** Deterministic UTF-16 code-unit ordering, independent of host locale. */
|
|
144
|
+
function compareCodeUnits(left, right) {
|
|
145
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
146
|
+
}
|
|
147
|
+
function stringify(value) {
|
|
148
|
+
if (value === void 0) return "null";
|
|
149
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") return JSON.stringify(value);
|
|
150
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
151
|
+
if (Array.isArray(value)) return `[${value.map(stringify).join(",")}]`;
|
|
152
|
+
if (typeof value === "object") {
|
|
153
|
+
const record = value;
|
|
154
|
+
const parts = [];
|
|
155
|
+
for (const key of Object.keys(record).sort()) {
|
|
156
|
+
if (record[key] === void 0) continue;
|
|
157
|
+
parts.push(`${JSON.stringify(key)}:${stringify(record[key])}`);
|
|
158
|
+
}
|
|
159
|
+
return `{${parts.join(",")}}`;
|
|
80
160
|
}
|
|
81
|
-
|
|
161
|
+
throw new Error(`Cannot canonicalize a ${typeof value} value.`);
|
|
82
162
|
}
|
|
83
163
|
//#endregion
|
|
84
|
-
//#region src/
|
|
164
|
+
//#region src/lib/core/reference-graph.ts
|
|
165
|
+
/**
|
|
166
|
+
* Ask each asset's owning plugin for the references in its parameters. Assets
|
|
167
|
+
* whose type is unknown or whose parameters do not validate contribute no
|
|
168
|
+
* references — those problems are reported by the regular validation pass.
|
|
169
|
+
*/
|
|
170
|
+
function collectAllReferences(library, plugins) {
|
|
171
|
+
const referencesByAsset = {};
|
|
172
|
+
for (const [assetId, record] of Object.entries(library.assets)) {
|
|
173
|
+
referencesByAsset[assetId] = [];
|
|
174
|
+
const plugin = plugins[record.type];
|
|
175
|
+
if (!plugin?.collectReferences) continue;
|
|
176
|
+
try {
|
|
177
|
+
referencesByAsset[assetId] = plugin.collectReferences(plugin.validateParameters(record.parameters));
|
|
178
|
+
} catch {}
|
|
179
|
+
}
|
|
180
|
+
return referencesByAsset;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Reference *errors* (they block build/codegen): a reference to an unknown
|
|
184
|
+
* asset, a target whose type is not among the field's expected types, and
|
|
185
|
+
* cycles (composition is acyclic by nature).
|
|
186
|
+
*/
|
|
187
|
+
function referenceErrors(library, referencesByAsset) {
|
|
188
|
+
const issues = [];
|
|
189
|
+
for (const [assetId, references] of Object.entries(referencesByAsset)) for (const reference of references) {
|
|
190
|
+
if (!Object.hasOwn(library.assets, reference.assetId)) {
|
|
191
|
+
issues.push({
|
|
192
|
+
assetId,
|
|
193
|
+
message: `Reference "${reference.field}" points at unknown asset "${reference.assetId}".`
|
|
194
|
+
});
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const targetType = library.assets[reference.assetId].type;
|
|
198
|
+
if (reference.expectedTypes.length > 0 && !reference.expectedTypes.includes(targetType)) issues.push({
|
|
199
|
+
assetId,
|
|
200
|
+
message: `Reference "${reference.field}" points at "${reference.assetId}" of type "${targetType}" — expected ${reference.expectedTypes.map((type) => `"${type}"`).join(" or ")}.`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
issues.push(...cycleErrors(referencesByAsset));
|
|
204
|
+
return issues;
|
|
205
|
+
}
|
|
206
|
+
/** DFS cycle detection with an explicit stack path; each cycle reported once. */
|
|
207
|
+
function cycleErrors(referencesByAsset) {
|
|
208
|
+
const issues = [];
|
|
209
|
+
const finished = /* @__PURE__ */ new Set();
|
|
210
|
+
const reportedCycles = /* @__PURE__ */ new Set();
|
|
211
|
+
const stack = [];
|
|
212
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
213
|
+
function visit(assetId) {
|
|
214
|
+
if (finished.has(assetId)) return;
|
|
215
|
+
if (onStack.has(assetId)) {
|
|
216
|
+
const cyclePath = [...stack.slice(stack.indexOf(assetId)), assetId];
|
|
217
|
+
const cycleKey = [...new Set(cyclePath)].sort().join("\0");
|
|
218
|
+
if (!reportedCycles.has(cycleKey)) {
|
|
219
|
+
reportedCycles.add(cycleKey);
|
|
220
|
+
issues.push({
|
|
221
|
+
assetId: cyclePath[0],
|
|
222
|
+
message: `Reference cycle: ${cyclePath.map((id) => `"${id}"`).join(" → ")} — references express composition and must stay acyclic.`
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
stack.push(assetId);
|
|
228
|
+
onStack.add(assetId);
|
|
229
|
+
for (const reference of referencesByAsset[assetId] ?? []) if (Object.hasOwn(referencesByAsset, reference.assetId)) visit(reference.assetId);
|
|
230
|
+
stack.pop();
|
|
231
|
+
onStack.delete(assetId);
|
|
232
|
+
finished.add(assetId);
|
|
233
|
+
}
|
|
234
|
+
for (const assetId of Object.keys(referencesByAsset)) visit(assetId);
|
|
235
|
+
return issues;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The transitive reference closure of a set of assets, including the starting
|
|
239
|
+
* set — the assets that must be delivered so the starting set renders as
|
|
240
|
+
* authored. Tolerates cycles and ignores dangling targets (both are reported
|
|
241
|
+
* as errors by `referenceErrors`).
|
|
242
|
+
*/
|
|
243
|
+
function referenceClosure(assetIds, referencesByAsset) {
|
|
244
|
+
const closure = /* @__PURE__ */ new Set();
|
|
245
|
+
const pending = [...assetIds];
|
|
246
|
+
while (pending.length > 0) {
|
|
247
|
+
const assetId = pending.pop();
|
|
248
|
+
if (closure.has(assetId) || !Object.hasOwn(referencesByAsset, assetId)) continue;
|
|
249
|
+
closure.add(assetId);
|
|
250
|
+
for (const reference of referencesByAsset[assetId]) pending.push(reference.assetId);
|
|
251
|
+
}
|
|
252
|
+
return closure;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A dependencies-first order over `assetIds` and everything they transitively
|
|
256
|
+
* reference — the build order that lets each entry inline its references from
|
|
257
|
+
* already-built entries. Throws on a cycle (with the cycle path).
|
|
258
|
+
*/
|
|
259
|
+
function topologicalReferenceOrder(assetIds, referencesByAsset) {
|
|
260
|
+
const order = [];
|
|
261
|
+
const finished = /* @__PURE__ */ new Set();
|
|
262
|
+
const stack = [];
|
|
263
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
264
|
+
function visit(assetId) {
|
|
265
|
+
if (finished.has(assetId) || !Object.hasOwn(referencesByAsset, assetId)) return;
|
|
266
|
+
if (onStack.has(assetId)) {
|
|
267
|
+
const cyclePath = [...stack.slice(stack.indexOf(assetId)), assetId];
|
|
268
|
+
throw new Error(`Reference cycle: ${cyclePath.map((id) => `"${id}"`).join(" → ")} — cannot order entries.`);
|
|
269
|
+
}
|
|
270
|
+
stack.push(assetId);
|
|
271
|
+
onStack.add(assetId);
|
|
272
|
+
for (const reference of referencesByAsset[assetId]) visit(reference.assetId);
|
|
273
|
+
stack.pop();
|
|
274
|
+
onStack.delete(assetId);
|
|
275
|
+
finished.add(assetId);
|
|
276
|
+
order.push(assetId);
|
|
277
|
+
}
|
|
278
|
+
for (const assetId of assetIds) visit(assetId);
|
|
279
|
+
return order;
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/lib/core/manifest-membership.ts
|
|
283
|
+
/**
|
|
284
|
+
* Compute the full membership picture from a library and its collected
|
|
285
|
+
* references: for each manifest the explicit members, the passive members
|
|
286
|
+
* (`referenceClosure(explicit) − explicit`) with their via-attribution, and
|
|
287
|
+
* the resulting delivery set.
|
|
288
|
+
*/
|
|
289
|
+
function computeLibraryMembership(library, referencesByAsset) {
|
|
290
|
+
const manifestNames = new Set(Object.keys(library.manifests));
|
|
291
|
+
for (const record of Object.values(library.assets)) for (const manifestName of record.manifests) manifestNames.add(manifestName);
|
|
292
|
+
const membership = {};
|
|
293
|
+
for (const manifestName of [...manifestNames].sort(compareCodeUnits)) {
|
|
294
|
+
const explicitMemberIds = Object.entries(library.assets).filter(([, record]) => record.manifests.includes(manifestName)).map(([assetId]) => assetId).sort(compareCodeUnits);
|
|
295
|
+
const explicitMemberSet = new Set(explicitMemberIds);
|
|
296
|
+
const deliveredAssetIds = new Set(explicitMemberIds);
|
|
297
|
+
const viaByPassiveAssetId = /* @__PURE__ */ new Map();
|
|
298
|
+
for (const explicitMemberId of explicitMemberIds) for (const reachedAssetId of referenceClosure([explicitMemberId], referencesByAsset)) {
|
|
299
|
+
deliveredAssetIds.add(reachedAssetId);
|
|
300
|
+
if (explicitMemberSet.has(reachedAssetId)) continue;
|
|
301
|
+
const via = viaByPassiveAssetId.get(reachedAssetId) ?? [];
|
|
302
|
+
via.push(explicitMemberId);
|
|
303
|
+
viaByPassiveAssetId.set(reachedAssetId, via);
|
|
304
|
+
}
|
|
305
|
+
membership[manifestName] = {
|
|
306
|
+
explicitMemberIds,
|
|
307
|
+
passiveMembers: [...viaByPassiveAssetId.entries()].map(([assetId, via]) => ({
|
|
308
|
+
assetId,
|
|
309
|
+
via
|
|
310
|
+
})).sort((a, b) => compareCodeUnits(a.assetId, b.assetId)),
|
|
311
|
+
deliveredAssetIds
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return membership;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Every asset delivered by at least one manifest, explicitly or passively —
|
|
318
|
+
* the processing set (assets outside it are inert). An asset is
|
|
319
|
+
* *unreachable* (nothing delivers it) exactly when it is missing here.
|
|
320
|
+
*/
|
|
321
|
+
function allDeliveredAssetIds(membership) {
|
|
322
|
+
const delivered = /* @__PURE__ */ new Set();
|
|
323
|
+
for (const manifestMembership of Object.values(membership)) for (const assetId of manifestMembership.deliveredAssetIds) delivered.add(assetId);
|
|
324
|
+
return delivered;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/lib/server/processing/ffmpeg.ts
|
|
85
328
|
/**
|
|
86
329
|
* The ffmpeg/ffprobe toolchain layer: binary resolution (bundled `-static`
|
|
87
330
|
* packages by default, with host-config overrides) and a spawn helper.
|
|
@@ -117,7 +360,7 @@ async function runToolchainBinary(binaryPath, commandArguments) {
|
|
|
117
360
|
}
|
|
118
361
|
}
|
|
119
362
|
//#endregion
|
|
120
|
-
//#region src/processing/media-probe.ts
|
|
363
|
+
//#region src/lib/server/processing/media-probe.ts
|
|
121
364
|
/** Parse ffprobe's JSON output (pure, testable without a binary). */
|
|
122
365
|
function parseProbeOutput(json, filePath) {
|
|
123
366
|
const output = JSON.parse(json);
|
|
@@ -144,13 +387,13 @@ async function probeMedia(filePath, config) {
|
|
|
144
387
|
]), filePath);
|
|
145
388
|
}
|
|
146
389
|
//#endregion
|
|
147
|
-
//#region src/processing/audio-processing.ts
|
|
390
|
+
//#region src/lib/server/processing/audio-processing.ts
|
|
148
391
|
/**
|
|
149
392
|
* The ffmpeg transcode step of the audio plugin: one audio-only proxy per
|
|
150
393
|
* preset (opus/webm or aac/m4a). Deterministic like the video step:
|
|
151
394
|
* metadata stripped, bitexact muxing.
|
|
152
395
|
*/
|
|
153
|
-
|
|
396
|
+
var ENCODER_BY_CODEC = {
|
|
154
397
|
opus: "libopus",
|
|
155
398
|
aac: "aac"
|
|
156
399
|
};
|
|
@@ -187,7 +430,7 @@ async function processAudioFile(options) {
|
|
|
187
430
|
return { durationMs: (await probeMedia(options.targetPath, options.config)).durationMs };
|
|
188
431
|
}
|
|
189
432
|
//#endregion
|
|
190
|
-
//#region src/plugins/contract.ts
|
|
433
|
+
//#region src/lib/server/plugins/contract.ts
|
|
191
434
|
/** Resolve a named preset out of a plugin's merged preset map, or throw listing the known names. */
|
|
192
435
|
function resolveNamedPreset(label, presetName, presets) {
|
|
193
436
|
const preset = presets[presetName];
|
|
@@ -201,12 +444,12 @@ function requireProxyField(proxy, field) {
|
|
|
201
444
|
return value;
|
|
202
445
|
}
|
|
203
446
|
//#endregion
|
|
204
|
-
//#region src/plugins/audio.ts
|
|
447
|
+
//#region src/lib/server/plugins/audio.ts
|
|
205
448
|
/**
|
|
206
449
|
* Audio plugin (server part): one ffmpeg transcode per preset.
|
|
207
450
|
* No dimensions — the proxy's only output metadata is its duration.
|
|
208
451
|
*/
|
|
209
|
-
|
|
452
|
+
var audioServerPlugin = {
|
|
210
453
|
descriptor: audioPlugin,
|
|
211
454
|
pluginConfig: (config) => config.plugins.audio,
|
|
212
455
|
presets: (config) => config.plugins.audio.presets,
|
|
@@ -226,7 +469,7 @@ const audioServerPlugin = {
|
|
|
226
469
|
})
|
|
227
470
|
};
|
|
228
471
|
//#endregion
|
|
229
|
-
//#region src/processing/video-processing.ts
|
|
472
|
+
//#region src/lib/server/processing/video-processing.ts
|
|
230
473
|
/**
|
|
231
474
|
* The ffmpeg transcode step shared by the video and chroma-key-video plugins
|
|
232
475
|
* (chroma-key-video builds on video's processing; its keying data is
|
|
@@ -309,14 +552,14 @@ async function processVideoFile(options) {
|
|
|
309
552
|
};
|
|
310
553
|
}
|
|
311
554
|
//#endregion
|
|
312
|
-
//#region src/plugins/chroma-key-video.ts
|
|
555
|
+
//#region src/lib/server/plugins/chroma-key-video.ts
|
|
313
556
|
/**
|
|
314
557
|
* Chroma-key-video plugin (server part): shares the video plugin's transcode
|
|
315
558
|
* pipeline and preset namespace — the keying parameters and the
|
|
316
559
|
* mask/fallback references never reach ffmpeg, they are entry-only data
|
|
317
560
|
* (the descriptor's `processingParameters` keeps them out of the job hash).
|
|
318
561
|
*/
|
|
319
|
-
|
|
562
|
+
var chromaKeyVideoServerPlugin = {
|
|
320
563
|
descriptor: chromaKeyVideoPlugin,
|
|
321
564
|
pluginConfig: (config) => config.plugins.video,
|
|
322
565
|
presets: (config) => config.plugins.video.presets,
|
|
@@ -342,7 +585,7 @@ const chromaKeyVideoServerPlugin = {
|
|
|
342
585
|
})
|
|
343
586
|
};
|
|
344
587
|
//#endregion
|
|
345
|
-
//#region src/processing/image-processing.ts
|
|
588
|
+
//#region src/lib/server/processing/image-processing.ts
|
|
346
589
|
/**
|
|
347
590
|
* The sharp step shared by the image and slice plugins: optional resize to a
|
|
348
591
|
* target width, format conversion per preset, write the proxy file. Returns
|
|
@@ -369,7 +612,7 @@ async function readImageDimensions(filePath) {
|
|
|
369
612
|
};
|
|
370
613
|
}
|
|
371
614
|
//#endregion
|
|
372
|
-
//#region src/plugins/image.ts
|
|
615
|
+
//#region src/lib/server/plugins/image.ts
|
|
373
616
|
/**
|
|
374
617
|
* Image plugin (server part): sharp scale + convert per preset.
|
|
375
618
|
*/
|
|
@@ -377,7 +620,7 @@ async function readImageDimensions(filePath) {
|
|
|
377
620
|
function extensionForImageFormat(format) {
|
|
378
621
|
return format === "jpeg" ? "jpg" : format;
|
|
379
622
|
}
|
|
380
|
-
|
|
623
|
+
var imageServerPlugin = {
|
|
381
624
|
descriptor: imagePlugin,
|
|
382
625
|
pluginConfig: (config) => config.plugins.image,
|
|
383
626
|
presets: (config) => config.plugins.image.presets,
|
|
@@ -398,7 +641,7 @@ const imageServerPlugin = {
|
|
|
398
641
|
})
|
|
399
642
|
};
|
|
400
643
|
//#endregion
|
|
401
|
-
//#region src/plugins/slice.ts
|
|
644
|
+
//#region src/lib/server/plugins/slice.ts
|
|
402
645
|
/**
|
|
403
646
|
* Slice plugin (server part): the same sharp conversion as the image plugin,
|
|
404
647
|
* never resized — insets are authored in source pixels, so the delivered proxy
|
|
@@ -418,7 +661,7 @@ function detectPixelRatio(fileName) {
|
|
|
418
661
|
const ratio = match ? Number(match[1]) : 1;
|
|
419
662
|
return ratio >= 1 ? ratio : 1;
|
|
420
663
|
}
|
|
421
|
-
|
|
664
|
+
var sliceServerPlugin = {
|
|
422
665
|
descriptor: slicePlugin,
|
|
423
666
|
pluginConfig: (config) => config.plugins.slice,
|
|
424
667
|
presets: (config) => config.plugins.slice.presets,
|
|
@@ -454,11 +697,11 @@ const sliceServerPlugin = {
|
|
|
454
697
|
}
|
|
455
698
|
};
|
|
456
699
|
//#endregion
|
|
457
|
-
//#region src/plugins/video.ts
|
|
700
|
+
//#region src/lib/server/plugins/video.ts
|
|
458
701
|
/**
|
|
459
702
|
* Video plugin (server part): one ffmpeg transcode per preset.
|
|
460
703
|
*/
|
|
461
|
-
|
|
704
|
+
var videoServerPlugin = {
|
|
462
705
|
descriptor: videoPlugin,
|
|
463
706
|
pluginConfig: (config) => config.plugins.video,
|
|
464
707
|
presets: (config) => config.plugins.video.presets,
|
|
@@ -481,8 +724,8 @@ const videoServerPlugin = {
|
|
|
481
724
|
})
|
|
482
725
|
};
|
|
483
726
|
//#endregion
|
|
484
|
-
//#region src/plugins.ts
|
|
485
|
-
|
|
727
|
+
//#region src/lib/server/plugins.ts
|
|
728
|
+
var serverPluginsByType = {
|
|
486
729
|
[imageServerPlugin.descriptor.type]: imageServerPlugin,
|
|
487
730
|
[sliceServerPlugin.descriptor.type]: sliceServerPlugin,
|
|
488
731
|
[videoServerPlugin.descriptor.type]: videoServerPlugin,
|
|
@@ -498,8 +741,7 @@ function serverPluginForType(type, registry = serverPluginsByType) {
|
|
|
498
741
|
function descriptorsByType(registry) {
|
|
499
742
|
return Object.fromEntries(Object.entries(registry).map(([type, plugin]) => [type, plugin.descriptor]));
|
|
500
743
|
}
|
|
501
|
-
|
|
502
|
-
const pluginsByType = descriptorsByType(serverPluginsByType);
|
|
744
|
+
descriptorsByType(serverPluginsByType);
|
|
503
745
|
/**
|
|
504
746
|
* The job-hash parameter input for an asset: the plugin-declared
|
|
505
747
|
* processing-relevant subset when the plugin distinguishes one (chroma-key-
|
|
@@ -509,7 +751,7 @@ function processingParametersOf(plugin, parameters) {
|
|
|
509
751
|
return plugin.processingParameters ? plugin.processingParameters(parameters) : parameters;
|
|
510
752
|
}
|
|
511
753
|
//#endregion
|
|
512
|
-
//#region src/codegen.ts
|
|
754
|
+
//#region src/lib/server/codegen.ts
|
|
513
755
|
/**
|
|
514
756
|
* Manifest module codegen: one `<manifest-name-kebab>.generated.ts`
|
|
515
757
|
* per manifest in the configured manifests directory, containing data only —
|
|
@@ -517,7 +759,7 @@ function processingParametersOf(plugin, parameters) {
|
|
|
517
759
|
* sorted asset ids, fixed property order, tab indentation. Generated modules
|
|
518
760
|
* for deleted manifests are removed (only files carrying the fundus header).
|
|
519
761
|
*/
|
|
520
|
-
|
|
762
|
+
var GENERATED_HEADER = "// generated by fundus — do not edit\n";
|
|
521
763
|
/** `mainCatalog` → `main-catalog`. */
|
|
522
764
|
function kebabCaseFromCamel(name) {
|
|
523
765
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
@@ -664,7 +906,7 @@ function jobHash(rawHash, parameters, pipelineVersion = 1) {
|
|
|
664
906
|
return createHash("sha256").update(rawHash + canonicalizeParameters(parameters) + String(pipelineVersion)).digest("hex").slice(0, 8);
|
|
665
907
|
}
|
|
666
908
|
//#endregion
|
|
667
|
-
//#region src/folder-filesystem.ts
|
|
909
|
+
//#region src/lib/server/folder-filesystem.ts
|
|
668
910
|
async function ensureManagedFolder(root, folder) {
|
|
669
911
|
const directory = join(root, folder);
|
|
670
912
|
await mkdir(directory, { recursive: true });
|
|
@@ -691,7 +933,7 @@ function encodeRelativePathForUrl(path) {
|
|
|
691
933
|
return path.split("/").map(encodeURIComponent).join("/");
|
|
692
934
|
}
|
|
693
935
|
//#endregion
|
|
694
|
-
//#region src/processing/processor.ts
|
|
936
|
+
//#region src/lib/server/processing/processor.ts
|
|
695
937
|
/**
|
|
696
938
|
* The processing pipeline: every delivered asset gets exactly one
|
|
697
939
|
* proxy named `<assetId>.<jobHash>.<extension>`. Delivered = explicit or
|
|
@@ -708,7 +950,7 @@ function encodeRelativePathForUrl(path) {
|
|
|
708
950
|
* drive the pipeline with fixture asset types.
|
|
709
951
|
*/
|
|
710
952
|
/** Proxy file name shape; its id segment is pinned to core's asset-id grammar. */
|
|
711
|
-
|
|
953
|
+
var PROXY_FILE_PATTERN = /^[a-z][A-Za-z0-9]*\.[0-9a-f]{8}\.[a-z0-9]+$/;
|
|
712
954
|
/** Whether a file name in the proxy directory is (shaped like) a fundus proxy. */
|
|
713
955
|
function isProxyFileName(fileName) {
|
|
714
956
|
return PROXY_FILE_PATTERN.test(fileName);
|
|
@@ -845,7 +1087,7 @@ async function processAssets(config, library, registry = serverPluginsByType) {
|
|
|
845
1087
|
return report;
|
|
846
1088
|
}
|
|
847
1089
|
//#endregion
|
|
848
|
-
//#region src/library-store.ts
|
|
1090
|
+
//#region src/lib/server/library-store.ts
|
|
849
1091
|
/**
|
|
850
1092
|
* The fs-backed store over the library file (SSOT) and the raw directory.
|
|
851
1093
|
* Every mutation is load → modify → deterministic write, so the file on disk
|
|
@@ -853,7 +1095,7 @@ async function processAssets(config, library, registry = serverPluginsByType) {
|
|
|
853
1095
|
* store only removes them on asset deletion (the pipeline also prunes orphans).
|
|
854
1096
|
*/
|
|
855
1097
|
/** A single path segment with no traversal — guards against `../` escapes. */
|
|
856
|
-
|
|
1098
|
+
var SAFE_SEGMENT = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
857
1099
|
function assertSafeFileName(fileName) {
|
|
858
1100
|
if (!SAFE_SEGMENT.test(fileName) || fileName.includes("/") || fileName.includes("\\")) throw new Error(`Unsafe file name "${fileName}".`);
|
|
859
1101
|
}
|
|
@@ -1450,348 +1692,6 @@ var LibraryStore = class {
|
|
|
1450
1692
|
if (collision !== void 0) throw new Error(`Folder "${path}" collides with existing folder "${collision}".`);
|
|
1451
1693
|
}
|
|
1452
1694
|
};
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
* The one operations layer: builds the state payload and runs
|
|
1457
|
-
* every mutation through the autosave chain — write library → process affected
|
|
1458
|
-
* assets → regenerate codegen → return fresh state. Both the REST server
|
|
1459
|
-
* and the CLI consume this class, so every
|
|
1460
|
-
* operation has exactly one implementation and there is no other save path.
|
|
1461
|
-
*/
|
|
1462
|
-
/**
|
|
1463
|
-
* The CLI's `--parameters` merge semantics (M7): a SHALLOW merge at the top
|
|
1464
|
-
* level onto the current parameters — a nested object value replaces the whole
|
|
1465
|
-
* current object under that key, a top-level `null` clears an optional field,
|
|
1466
|
-
* and an asset reference is set with the normal `{"$asset": "<id>"}` marker.
|
|
1467
|
-
* The merged result still passes the owning plugin's full validation.
|
|
1468
|
-
*/
|
|
1469
|
-
function mergeParameterPatch(currentParameters, patch) {
|
|
1470
|
-
if (typeof patch !== "object" || patch === null || Array.isArray(patch)) throw new Error("The parameter patch must be a JSON object, e.g. {\"preset\": \"default\"}.");
|
|
1471
|
-
const merged = typeof currentParameters === "object" && currentParameters !== null && !Array.isArray(currentParameters) ? { ...currentParameters } : {};
|
|
1472
|
-
for (const [key, value] of Object.entries(patch)) if (value === null) delete merged[key];
|
|
1473
|
-
else merged[key] = value;
|
|
1474
|
-
return merged;
|
|
1475
|
-
}
|
|
1476
|
-
var FundusOperations = class {
|
|
1477
|
-
#config;
|
|
1478
|
-
#registry;
|
|
1479
|
-
#store;
|
|
1480
|
-
constructor(config, registry = serverPluginsByType) {
|
|
1481
|
-
this.#config = config;
|
|
1482
|
-
this.#registry = registry;
|
|
1483
|
-
this.#store = new LibraryStore(config, registry);
|
|
1484
|
-
}
|
|
1485
|
-
get config() {
|
|
1486
|
-
return this.#config;
|
|
1487
|
-
}
|
|
1488
|
-
get store() {
|
|
1489
|
-
return this.#store;
|
|
1490
|
-
}
|
|
1491
|
-
/** Read-only state snapshot (no processing). */
|
|
1492
|
-
async state() {
|
|
1493
|
-
return this.#buildState(await this.#store.read(), []);
|
|
1494
|
-
}
|
|
1495
|
-
/**
|
|
1496
|
-
* Ingest an upload: store the raw file + record, then let the owning
|
|
1497
|
-
* plugin's optional `seedIngestParameters` derive better starting
|
|
1498
|
-
* parameters from the stored raw file (slices: insets from the image
|
|
1499
|
-
* dimensions, pixel ratio from the file name — the schema default can't
|
|
1500
|
-
* know either). The CLI additionally applies a parameter patch and/or an
|
|
1501
|
-
* explicit membership set in the same operation; a failing application
|
|
1502
|
-
* rolls the whole ingest back, so a failed ingest leaves nothing behind.
|
|
1503
|
-
*/
|
|
1504
|
-
async ingest(options) {
|
|
1505
|
-
let library = await this.#store.ingest(options);
|
|
1506
|
-
try {
|
|
1507
|
-
const plugin = serverPluginForType(options.type, this.#registry);
|
|
1508
|
-
if (plugin.seedIngestParameters) {
|
|
1509
|
-
const record = library.assets[options.id];
|
|
1510
|
-
const seeded = await plugin.seedIngestParameters({
|
|
1511
|
-
rawPath: join(this.#config.rawDir, record.raw),
|
|
1512
|
-
rawFileName: rawFileNameOfPath(record.raw),
|
|
1513
|
-
parameters: plugin.descriptor.validateParameters(record.parameters),
|
|
1514
|
-
config: this.#config
|
|
1515
|
-
});
|
|
1516
|
-
if (seeded !== void 0) library = await this.#store.updateParameters(options.id, seeded);
|
|
1517
|
-
}
|
|
1518
|
-
if (options.parametersPatch !== void 0 || options.manifests !== void 0) library = await this.#applyAssetChanges(library, options.id, {
|
|
1519
|
-
parametersPatch: options.parametersPatch,
|
|
1520
|
-
manifests: options.manifests
|
|
1521
|
-
});
|
|
1522
|
-
} catch (cause) {
|
|
1523
|
-
await this.#store.deleteAsset(options.id);
|
|
1524
|
-
throw cause;
|
|
1525
|
-
}
|
|
1526
|
-
return this.#afterMutation(library);
|
|
1527
|
-
}
|
|
1528
|
-
/** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
|
|
1529
|
-
async patchAsset(id, body) {
|
|
1530
|
-
let library = await this.#store.read();
|
|
1531
|
-
this.#requireAsset(library, id);
|
|
1532
|
-
if (body.parameters !== void 0) library = await this.#store.updateParameters(id, body.parameters);
|
|
1533
|
-
if (body.manifests !== void 0) {
|
|
1534
|
-
if (!Array.isArray(body.manifests) || body.manifests.some((name) => typeof name !== "string")) throw new Error("\"manifests\" must be an array of manifest names.");
|
|
1535
|
-
library = await this.#store.setManifests(id, body.manifests);
|
|
1536
|
-
}
|
|
1537
|
-
if (body.renameTo !== void 0) {
|
|
1538
|
-
if (typeof body.renameTo !== "string") throw new Error("\"renameTo\" must be a string.");
|
|
1539
|
-
library = await this.#store.renameAsset(id, body.renameTo);
|
|
1540
|
-
}
|
|
1541
|
-
if (body.folder !== void 0) {
|
|
1542
|
-
if (typeof body.folder !== "string") throw new Error("\"folder\" must be a string.");
|
|
1543
|
-
library = await this.#store.moveAsset(body.renameTo ?? id, body.folder);
|
|
1544
|
-
}
|
|
1545
|
-
return this.#afterMutation(library);
|
|
1546
|
-
}
|
|
1547
|
-
async moveAsset(id, folder) {
|
|
1548
|
-
return this.#afterMutation(await this.#store.moveAsset(id, folder));
|
|
1549
|
-
}
|
|
1550
|
-
/** Move a selected asset set as one editor action and roll back on any failure. */
|
|
1551
|
-
async moveAssets(assetIds, folder) {
|
|
1552
|
-
const uniqueIds = [...new Set(assetIds)];
|
|
1553
|
-
if (uniqueIds.length === 0) throw new Error("At least one asset id is required.");
|
|
1554
|
-
const originalFolders = /* @__PURE__ */ new Map();
|
|
1555
|
-
const library = await this.#store.read();
|
|
1556
|
-
for (const id of uniqueIds) {
|
|
1557
|
-
this.#requireAsset(library, id);
|
|
1558
|
-
originalFolders.set(id, folderOfRawPath(library.assets[id].raw));
|
|
1559
|
-
}
|
|
1560
|
-
const movedIds = [];
|
|
1561
|
-
try {
|
|
1562
|
-
let updated = library;
|
|
1563
|
-
for (const id of uniqueIds) {
|
|
1564
|
-
if (originalFolders.get(id) === folder) continue;
|
|
1565
|
-
updated = await this.#store.moveAsset(id, folder);
|
|
1566
|
-
movedIds.push(id);
|
|
1567
|
-
}
|
|
1568
|
-
return this.#afterMutation(updated);
|
|
1569
|
-
} catch (cause) {
|
|
1570
|
-
for (const id of movedIds.reverse()) await this.#store.moveAsset(id, originalFolders.get(id) ?? "");
|
|
1571
|
-
throw cause;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
async createFolder(path) {
|
|
1575
|
-
return this.#afterMutation(await this.#store.createFolder(path));
|
|
1576
|
-
}
|
|
1577
|
-
async renameFolder(path, newName) {
|
|
1578
|
-
return this.#afterMutation(await this.#store.renameFolder(path, newName));
|
|
1579
|
-
}
|
|
1580
|
-
async moveFolder(path, parent) {
|
|
1581
|
-
return this.#afterMutation(await this.#store.moveFolder(path, parent));
|
|
1582
|
-
}
|
|
1583
|
-
async deleteFolder(path) {
|
|
1584
|
-
const result = await this.#store.deleteFolder(path);
|
|
1585
|
-
return this.#afterMutation(result.library);
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* CLI-facing asset update (M7): shallow-merge a parameter patch and/or
|
|
1589
|
-
* replace the explicit manifest membership — validating the *result* before
|
|
1590
|
-
* anything is persisted, so a change that would leave the library invalid
|
|
1591
|
-
* (dangling or cyclic reference, unknown manifest, invalid parameters) is
|
|
1592
|
-
* rejected with nothing written. The REST `patchAsset` keeps the editor's
|
|
1593
|
-
* lenient replace semantics.
|
|
1594
|
-
*/
|
|
1595
|
-
async setAsset(id, changes) {
|
|
1596
|
-
const library = await this.#store.read();
|
|
1597
|
-
this.#requireAsset(library, id);
|
|
1598
|
-
return this.#afterMutation(await this.#applyAssetChanges(library, id, changes));
|
|
1599
|
-
}
|
|
1600
|
-
/**
|
|
1601
|
-
* CLI-facing rename (M7): rejects a rename that would break references —
|
|
1602
|
-
* a referrer keeps pointing at the old id, so renaming a referenced asset
|
|
1603
|
-
* introduces dangling-reference errors. Update the referrers first.
|
|
1604
|
-
*/
|
|
1605
|
-
async renameAsset(id, newId) {
|
|
1606
|
-
const library = await this.#store.read();
|
|
1607
|
-
this.#requireAsset(library, id);
|
|
1608
|
-
if (newId !== id && !Object.hasOwn(library.assets, newId)) {
|
|
1609
|
-
const candidate = structuredClone(library);
|
|
1610
|
-
candidate.assets[newId] = candidate.assets[id];
|
|
1611
|
-
delete candidate.assets[id];
|
|
1612
|
-
await this.#assertNoIntroducedIssues(library, candidate);
|
|
1613
|
-
}
|
|
1614
|
-
return this.#afterMutation(await this.#store.renameAsset(id, newId));
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* Delete an asset: the record, its raw file, and its proxies. With
|
|
1618
|
-
* `failOnIntroducedIssues` (the CLI), a delete that would break references
|
|
1619
|
-
* (the asset is a reference target) is rejected before anything —
|
|
1620
|
-
* including the raw file — is removed; the REST route stays lenient.
|
|
1621
|
-
*/
|
|
1622
|
-
async deleteAsset(id, options) {
|
|
1623
|
-
if (options?.failOnIntroducedIssues) {
|
|
1624
|
-
const library = await this.#store.read();
|
|
1625
|
-
this.#requireAsset(library, id);
|
|
1626
|
-
const candidate = structuredClone(library);
|
|
1627
|
-
delete candidate.assets[id];
|
|
1628
|
-
await this.#assertNoIntroducedIssues(library, candidate);
|
|
1629
|
-
}
|
|
1630
|
-
return this.#afterMutation(await this.#store.deleteAsset(id));
|
|
1631
|
-
}
|
|
1632
|
-
/** Delete a selected asset set as one lenient editor mutation. */
|
|
1633
|
-
async deleteAssets(assetIds) {
|
|
1634
|
-
const uniqueIds = [...new Set(assetIds)];
|
|
1635
|
-
if (uniqueIds.length === 0) throw new Error("At least one asset id is required.");
|
|
1636
|
-
return this.#afterMutation(await this.#store.deleteAssets(uniqueIds));
|
|
1637
|
-
}
|
|
1638
|
-
async createManifest(name) {
|
|
1639
|
-
return this.#afterMutation(await this.#store.createManifest(name));
|
|
1640
|
-
}
|
|
1641
|
-
async setManifestAppearance(name, appearance) {
|
|
1642
|
-
return this.#afterMutation(await this.#store.setManifestAppearance(name, appearance));
|
|
1643
|
-
}
|
|
1644
|
-
/** Apply a PATCH body: appearance fields and/or a rename — in that order. */
|
|
1645
|
-
async patchManifest(name, body) {
|
|
1646
|
-
let library = await this.#store.setManifestAppearance(name, {
|
|
1647
|
-
color: body.color,
|
|
1648
|
-
emoji: body.emoji
|
|
1649
|
-
});
|
|
1650
|
-
if (body.renameTo !== void 0) {
|
|
1651
|
-
if (typeof body.renameTo !== "string") throw new Error("\"renameTo\" must be a string.");
|
|
1652
|
-
library = await this.#store.renameManifest(name, body.renameTo);
|
|
1653
|
-
}
|
|
1654
|
-
return this.#afterMutation(library);
|
|
1655
|
-
}
|
|
1656
|
-
/**
|
|
1657
|
-
* Rename a manifest: rewrites every asset's membership and replaces the
|
|
1658
|
-
* generated module (the old one is pruned as a stray). Host code importing
|
|
1659
|
-
* the module must be updated by hand — both the file name and the exported
|
|
1660
|
-
* const change with the manifest name.
|
|
1661
|
-
*/
|
|
1662
|
-
async renameManifest(name, newName) {
|
|
1663
|
-
return this.#afterMutation(await this.#store.renameManifest(name, newName));
|
|
1664
|
-
}
|
|
1665
|
-
async deleteManifest(name) {
|
|
1666
|
-
return this.#afterMutation(await this.#store.deleteManifest(name));
|
|
1667
|
-
}
|
|
1668
|
-
/** The autosave chain tail: process + regenerate, then report fresh state. */
|
|
1669
|
-
async #afterMutation(library) {
|
|
1670
|
-
const pipelineIssues = [];
|
|
1671
|
-
const issues = await this.#store.validate(library);
|
|
1672
|
-
if (issues.length === 0) try {
|
|
1673
|
-
const report = await processAssets(this.#config, library, this.#registry);
|
|
1674
|
-
await generateManifestModules(this.#config, library, report.proxies, this.#registry);
|
|
1675
|
-
} catch (cause) {
|
|
1676
|
-
pipelineIssues.push({ message: `Processing failed: ${cause instanceof Error ? cause.message : String(cause)}` });
|
|
1677
|
-
}
|
|
1678
|
-
return this.#buildState(library, pipelineIssues, issues);
|
|
1679
|
-
}
|
|
1680
|
-
async #buildState(library, extraIssues, precomputedIssues) {
|
|
1681
|
-
const issues = [...precomputedIssues ?? await this.#store.validate(library), ...extraIssues];
|
|
1682
|
-
const referencesByAsset = collectAllReferences(library, descriptorsByType(this.#registry));
|
|
1683
|
-
const membershipByManifest = computeLibraryMembership(library, referencesByAsset);
|
|
1684
|
-
const delivered = allDeliveredAssetIds(membershipByManifest);
|
|
1685
|
-
const passiveManifestsByAsset = /* @__PURE__ */ new Map();
|
|
1686
|
-
for (const manifestName of Object.keys(library.manifests).sort(compareCodeUnits)) for (const passiveMember of membershipByManifest[manifestName].passiveMembers) {
|
|
1687
|
-
const passiveManifests = passiveManifestsByAsset.get(passiveMember.assetId) ?? [];
|
|
1688
|
-
passiveManifests.push({
|
|
1689
|
-
manifest: manifestName,
|
|
1690
|
-
via: passiveMember.via
|
|
1691
|
-
});
|
|
1692
|
-
passiveManifestsByAsset.set(passiveMember.assetId, passiveManifests);
|
|
1693
|
-
}
|
|
1694
|
-
const incomingByAsset = /* @__PURE__ */ new Map();
|
|
1695
|
-
for (const [referrerId, references] of Object.entries(referencesByAsset)) for (const reference of references) {
|
|
1696
|
-
const incoming = incomingByAsset.get(reference.assetId) ?? [];
|
|
1697
|
-
incoming.push({
|
|
1698
|
-
field: reference.field,
|
|
1699
|
-
assetId: referrerId
|
|
1700
|
-
});
|
|
1701
|
-
incomingByAsset.set(reference.assetId, incoming);
|
|
1702
|
-
}
|
|
1703
|
-
const assets = [];
|
|
1704
|
-
for (const assetId of Object.keys(library.assets).sort(compareCodeUnits)) {
|
|
1705
|
-
const record = library.assets[assetId];
|
|
1706
|
-
const warnings = [];
|
|
1707
|
-
for (const issue of issues) if (issue.assetId === assetId) warnings.push(issue.message);
|
|
1708
|
-
if (record.manifests.length === 0 && !delivered.has(assetId)) warnings.push("In no manifest and not referenced by any delivered asset — nothing is processed or generated for it.");
|
|
1709
|
-
let proxy = null;
|
|
1710
|
-
if (delivered.has(assetId)) try {
|
|
1711
|
-
const currentProxy = await readCurrentProxy(assetId, record, this.#config, this.#registry);
|
|
1712
|
-
if (currentProxy) proxy = {
|
|
1713
|
-
url: `/files/proxy/${encodeRelativePathForUrl(currentProxy.relativePath)}`,
|
|
1714
|
-
upToDate: true,
|
|
1715
|
-
...currentProxy
|
|
1716
|
-
};
|
|
1717
|
-
else warnings.push("Proxy is stale or missing — it will be (re)processed on the next save.");
|
|
1718
|
-
} catch (cause) {
|
|
1719
|
-
warnings.push(cause instanceof Error ? cause.message : String(cause));
|
|
1720
|
-
}
|
|
1721
|
-
assets.push({
|
|
1722
|
-
id: assetId,
|
|
1723
|
-
type: record.type,
|
|
1724
|
-
raw: record.raw,
|
|
1725
|
-
folder: folderOfRawPath(record.raw),
|
|
1726
|
-
rawUrl: `/files/raw/${encodeRelativePathForUrl(record.raw)}`,
|
|
1727
|
-
rawHash: record.rawHash,
|
|
1728
|
-
parameters: record.parameters,
|
|
1729
|
-
manifests: record.manifests,
|
|
1730
|
-
passiveManifests: passiveManifestsByAsset.get(assetId) ?? [],
|
|
1731
|
-
proxy,
|
|
1732
|
-
warnings,
|
|
1733
|
-
references: {
|
|
1734
|
-
outgoing: referencesByAsset[assetId].map((reference) => ({
|
|
1735
|
-
field: reference.field,
|
|
1736
|
-
assetId: reference.assetId
|
|
1737
|
-
})),
|
|
1738
|
-
incoming: incomingByAsset.get(assetId) ?? []
|
|
1739
|
-
}
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
const proxySizeByAssetId = new Map(assets.filter((asset) => asset.proxy).map((asset) => [asset.id, asset.proxy?.fileSize ?? 0]));
|
|
1743
|
-
const manifestMemberships = {};
|
|
1744
|
-
const manifestDeliveredBytes = {};
|
|
1745
|
-
for (const manifestName of Object.keys(library.manifests).sort(compareCodeUnits)) {
|
|
1746
|
-
const membership = membershipByManifest[manifestName];
|
|
1747
|
-
manifestMemberships[manifestName] = {
|
|
1748
|
-
explicitMemberIds: membership.explicitMemberIds,
|
|
1749
|
-
passiveMembers: membership.passiveMembers
|
|
1750
|
-
};
|
|
1751
|
-
let deliveredBytes = 0;
|
|
1752
|
-
for (const deliveredAssetId of membership.deliveredAssetIds) deliveredBytes += proxySizeByAssetId.get(deliveredAssetId) ?? 0;
|
|
1753
|
-
manifestDeliveredBytes[manifestName] = deliveredBytes;
|
|
1754
|
-
}
|
|
1755
|
-
const presetNames = {};
|
|
1756
|
-
for (const [type, plugin] of Object.entries(this.#registry)) presetNames[type] = Object.keys(plugin.presets(this.#config)).sort();
|
|
1757
|
-
return {
|
|
1758
|
-
library,
|
|
1759
|
-
assets,
|
|
1760
|
-
issues,
|
|
1761
|
-
presetNames,
|
|
1762
|
-
manifestMemberships,
|
|
1763
|
-
manifestDeliveredBytes
|
|
1764
|
-
};
|
|
1765
|
-
}
|
|
1766
|
-
#requireAsset(library, id) {
|
|
1767
|
-
if (!Object.hasOwn(library.assets, id)) throw new Error(`Unknown asset "${id}".`);
|
|
1768
|
-
}
|
|
1769
|
-
/**
|
|
1770
|
-
* Apply a CLI asset change to the store, validating the resulting library
|
|
1771
|
-
* on an in-memory candidate first — nothing is persisted when the change
|
|
1772
|
-
* would introduce validation issues. Pre-existing issues never block an
|
|
1773
|
-
* unrelated mutation (only *introduced* issues are rejected).
|
|
1774
|
-
*/
|
|
1775
|
-
async #applyAssetChanges(library, id, changes) {
|
|
1776
|
-
const candidate = structuredClone(library);
|
|
1777
|
-
const candidateRecord = candidate.assets[id];
|
|
1778
|
-
if (changes.parametersPatch !== void 0) candidateRecord.parameters = serverPluginForType(candidateRecord.type, this.#registry).descriptor.validateParameters(mergeParameterPatch(candidateRecord.parameters, changes.parametersPatch));
|
|
1779
|
-
if (changes.manifests !== void 0) candidateRecord.manifests = [...new Set(changes.manifests)].sort();
|
|
1780
|
-
await this.#assertNoIntroducedIssues(library, candidate);
|
|
1781
|
-
let updated = library;
|
|
1782
|
-
if (changes.parametersPatch !== void 0) updated = await this.#store.updateParameters(id, candidateRecord.parameters);
|
|
1783
|
-
if (changes.manifests !== void 0) updated = await this.#store.setManifests(id, changes.manifests);
|
|
1784
|
-
return updated;
|
|
1785
|
-
}
|
|
1786
|
-
/** Reject a candidate library that has validation issues the current one lacks. */
|
|
1787
|
-
async #assertNoIntroducedIssues(library, candidate) {
|
|
1788
|
-
const existingIssueKeys = new Set((await this.#store.validate(library)).map((issue) => `${issue.assetId ?? ""}|${issue.message}`));
|
|
1789
|
-
const introduced = (await this.#store.validate(candidate)).filter((issue) => !existingIssueKeys.has(`${issue.assetId ?? ""}|${issue.message}`));
|
|
1790
|
-
if (introduced.length > 0) {
|
|
1791
|
-
const lines = introduced.map((issue) => ` - ${issue.assetId ? `[${issue.assetId}] ` : ""}${issue.message}`);
|
|
1792
|
-
throw new Error(`Rejected — the change would leave the library invalid (nothing was written):\n${lines.join("\n")}`);
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
};
|
|
1796
|
-
//#endregion
|
|
1797
|
-
export { runToolchainBinary as C, ffmpegBinaryPath as S, resolveConfig as T, strayGeneratedModuleNames as _, deliveredAssetIds as a, serverPluginForType as b, isProxyFileName as c, readProxyOutputInfo as d, listRelativeFiles as f, renderManifestModule as g, manifestModuleFileName as h, sha256Hex as i, processAssets as l, generateManifestModules as m, LibraryStore as n, expectedProxyFileName as o, buildManifestEntries as p, assertSafeRelativeFilePath as r, expectedProxyRelativePath as s, FundusOperations as t, readCurrentProxy as u, descriptorsByType as v, loadConfig as w, serverPluginsByType as x, pluginsByType as y };
|
|
1695
|
+
|
|
1696
|
+
export { LibraryStore as L, serverPluginForType as a, computeLibraryMembership as b, collectAllReferences as c, descriptorsByType as d, allDeliveredAssetIds as e, compareCodeUnits as f, generateManifestModules as g, encodeRelativePathForUrl as h, assertSafeRelativeFilePath as i, processAssets as p, readCurrentProxy as r, serverPluginsByType as s };
|
|
1697
|
+
//# sourceMappingURL=library-store.js-BP8cL-qD.js.map
|