@dxos/app-framework 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934
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/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/browser/{capability-OP63CD5N.mjs → capability-HRFPP52W.mjs} +11 -11
- package/dist/lib/browser/capability-HRFPP52W.mjs.map +7 -0
- package/dist/lib/browser/{capability-BBBBAPDI.mjs → capability-VQ236TY5.mjs} +10 -10
- package/dist/lib/browser/{chunk-T3Y4AEKX.mjs → chunk-23D4SJUE.mjs} +3 -3
- package/dist/lib/browser/{chunk-T3Y4AEKX.mjs.map → chunk-23D4SJUE.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-2CKCJ6PN.mjs → chunk-3JWJXGLK.mjs} +1 -1
- package/dist/lib/browser/{chunk-2CKCJ6PN.mjs.map → chunk-3JWJXGLK.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-I34GF4NG.mjs → chunk-45CHLTBV.mjs} +2 -2
- package/dist/lib/browser/{chunk-QSXYHXCE.mjs → chunk-66IXTIVK.mjs} +1 -1
- package/dist/lib/browser/{chunk-QSXYHXCE.mjs.map → chunk-66IXTIVK.mjs.map} +2 -2
- package/dist/lib/browser/chunk-6GZYCP73.mjs +422 -0
- package/dist/lib/browser/chunk-6GZYCP73.mjs.map +7 -0
- package/dist/lib/browser/chunk-7VZJR2OA.mjs +581 -0
- package/dist/lib/browser/chunk-7VZJR2OA.mjs.map +7 -0
- package/dist/lib/browser/{chunk-TGX63LTL.mjs → chunk-FJ4765WW.mjs} +1 -1
- package/dist/lib/browser/{chunk-TGX63LTL.mjs.map → chunk-FJ4765WW.mjs.map} +2 -2
- package/dist/lib/browser/chunk-FO3IYSLV.mjs +68 -0
- package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FU4GAFUQ.mjs → chunk-G6Q2M2M5.mjs} +85 -15
- package/dist/lib/browser/chunk-G6Q2M2M5.mjs.map +7 -0
- package/dist/lib/browser/chunk-J43JDWAV.mjs +1445 -0
- package/dist/lib/browser/chunk-J43JDWAV.mjs.map +7 -0
- package/dist/lib/browser/{chunk-JKWMHZP6.mjs → chunk-WBHCSOBW.mjs} +2 -2
- package/dist/lib/browser/chunk-WBHCSOBW.mjs.map +7 -0
- package/dist/lib/browser/chunk-XLYULXAO.mjs +469 -0
- package/dist/lib/browser/chunk-XLYULXAO.mjs.map +7 -0
- package/dist/lib/browser/{chunk-F7FW2RK2.mjs → chunk-ZGJAZSNE.mjs} +7 -32
- package/dist/lib/browser/chunk-ZGJAZSNE.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +14 -28
- package/dist/lib/browser/cli/index.mjs.map +3 -3
- package/dist/lib/browser/common/activation-events.mjs +7 -7
- package/dist/lib/browser/common/capabilities.mjs +7 -7
- package/dist/lib/browser/core/activation-event.mjs +1 -1
- package/dist/lib/browser/core/capability.mjs +1 -1
- package/dist/lib/browser/core/plugin-manager.mjs +8 -4
- package/dist/lib/browser/core/plugin.mjs +12 -2
- package/dist/lib/browser/core/url-loader.mjs +17 -5
- package/dist/lib/browser/index.mjs +38 -21
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/{invoker-capability-H5PPENOC.mjs → invoker-capability-CEL2L6GX.mjs} +12 -11
- package/dist/lib/browser/invoker-capability-CEL2L6GX.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +149 -28
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/browser/testing/react.mjs +78 -0
- package/dist/lib/browser/testing/react.mjs.map +7 -0
- package/dist/lib/browser/ui/index.mjs +18 -14
- package/dist/lib/node-esm/{capability-AWBEMRYR.mjs → capability-GS3XJH74.mjs} +10 -10
- package/dist/lib/node-esm/{capability-WFEG6CIZ.mjs → capability-I5P7ACFL.mjs} +11 -11
- package/dist/lib/node-esm/capability-I5P7ACFL.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-FKE4Z3D6.mjs → chunk-37Z53PXZ.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-FKE4Z3D6.mjs.map → chunk-37Z53PXZ.mjs.map} +2 -2
- package/dist/lib/node-esm/chunk-4RJL37GN.mjs +423 -0
- package/dist/lib/node-esm/chunk-4RJL37GN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs → chunk-6XW6LET6.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-URWHJQT2.mjs → chunk-D347W3KO.mjs} +7 -32
- package/dist/lib/node-esm/chunk-D347W3KO.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-DW45NFLW.mjs +1446 -0
- package/dist/lib/node-esm/chunk-DW45NFLW.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-EI4LYXFY.mjs +470 -0
- package/dist/lib/node-esm/chunk-EI4LYXFY.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ULUEXB7Q.mjs → chunk-KIFOQXD4.mjs} +85 -15
- package/dist/lib/node-esm/chunk-KIFOQXD4.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-VKHGNEDB.mjs → chunk-SBS2YMPT.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-VKHGNEDB.mjs.map → chunk-SBS2YMPT.mjs.map} +1 -1
- package/dist/lib/node-esm/{chunk-42KBWDE4.mjs → chunk-SDJ4B2LU.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-42KBWDE4.mjs.map → chunk-SDJ4B2LU.mjs.map} +1 -1
- package/dist/lib/node-esm/chunk-V24UWT36.mjs +582 -0
- package/dist/lib/node-esm/chunk-V24UWT36.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-G3RTFSNG.mjs → chunk-WFSRZKBP.mjs} +2 -2
- package/dist/lib/node-esm/chunk-WFSRZKBP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WK7OIQKI.mjs +70 -0
- package/dist/lib/node-esm/chunk-WK7OIQKI.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ZZ7CKK6W.mjs → chunk-XOCUANHO.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-ZZ7CKK6W.mjs.map → chunk-XOCUANHO.mjs.map} +2 -2
- package/dist/lib/node-esm/cli/index.mjs +14 -28
- package/dist/lib/node-esm/cli/index.mjs.map +3 -3
- package/dist/lib/node-esm/common/activation-events.mjs +7 -7
- package/dist/lib/node-esm/common/capabilities.mjs +7 -7
- package/dist/lib/node-esm/core/activation-event.mjs +1 -1
- package/dist/lib/node-esm/core/capability.mjs +1 -1
- package/dist/lib/node-esm/core/plugin-manager.mjs +8 -4
- package/dist/lib/node-esm/core/plugin.mjs +12 -2
- package/dist/lib/node-esm/core/url-loader.mjs +17 -5
- package/dist/lib/node-esm/index.mjs +38 -21
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/{invoker-capability-S3ZA527J.mjs → invoker-capability-QKUKN2MF.mjs} +12 -11
- package/dist/lib/node-esm/invoker-capability-QKUKN2MF.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +149 -28
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/testing/react.mjs +79 -0
- package/dist/lib/node-esm/testing/react.mjs.map +7 -0
- package/dist/lib/node-esm/ui/index.mjs +18 -14
- package/dist/plugin/node-esm/index.mjs +542 -33
- package/dist/plugin/node-esm/index.mjs.map +4 -4
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/types/src/common/annotations.d.ts +1 -0
- package/dist/types/src/common/annotations.d.ts.map +1 -0
- package/dist/types/src/common/capabilities.d.ts +2 -1
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/common/operations.d.ts +1 -1
- package/dist/types/src/common/operations.d.ts.map +1 -1
- package/dist/types/src/core/activation-event.d.ts +4 -4
- package/dist/types/src/core/activation-event.d.ts.map +1 -1
- package/dist/types/src/core/capability-manager.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +2 -2
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts +30 -0
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -0
- package/dist/types/src/core/index.d.ts +5 -0
- package/dist/types/src/core/index.d.ts.map +1 -1
- package/dist/types/src/core/plugin-asset-cache.d.ts +71 -0
- package/dist/types/src/core/plugin-asset-cache.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager.d.ts +230 -5
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manifest.d.ts +101 -0
- package/dist/types/src/core/plugin-manifest.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manifest.test.d.ts +2 -0
- package/dist/types/src/core/plugin-manifest.test.d.ts.map +1 -0
- package/dist/types/src/core/plugin.d.ts +164 -6
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/registry.d.ts +107 -0
- package/dist/types/src/core/registry.d.ts.map +1 -0
- package/dist/types/src/core/url-loader.d.ts +105 -3
- package/dist/types/src/core/url-loader.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/capability.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/errors.d.ts +6 -6
- package/dist/types/src/plugin-operation/history/errors.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/types.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/types.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/invoker-capability.d.ts +1 -1
- package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/meta.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/testing.d.ts +2 -1
- package/dist/types/src/plugin-operation/testing.d.ts.map +1 -1
- package/dist/types/src/plugin-runtime/capability.d.ts +1 -1
- package/dist/types/src/plugin-runtime/capability.d.ts.map +1 -1
- package/dist/types/src/plugin-runtime/meta.d.ts.map +1 -1
- package/dist/types/src/testing/harness.d.ts +67 -0
- package/dist/types/src/testing/harness.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/react.d.ts +27 -0
- package/dist/types/src/testing/react.d.ts.map +1 -0
- package/dist/types/src/testing/react.test.d.ts +2 -0
- package/dist/types/src/testing/react.test.d.ts.map +1 -0
- package/dist/types/src/testing/service.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts +64 -0
- package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts.map +1 -0
- package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts +19 -0
- package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/Placeholder/index.d.ts +2 -0
- package/dist/types/src/ui/components/Placeholder/index.d.ts.map +1 -0
- package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +16 -4
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/index.d.ts +16 -6
- package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.d.ts +110 -9
- package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.test.d.ts +2 -0
- package/dist/types/src/ui/components/Surface/types.test.d.ts.map +1 -0
- package/dist/types/src/ui/components/index.d.ts +1 -0
- package/dist/types/src/ui/components/index.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts +30 -3
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +34 -0
- package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/boot-loader/index.d.ts +2 -0
- package/dist/types/src/vite-plugin/boot-loader/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/boot-loader/loader.d.ts +51 -0
- package/dist/types/src/vite-plugin/boot-loader/loader.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/composer/index.d.ts +34 -0
- package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/import-map/index.d.ts +28 -0
- package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/index.d.ts +4 -2
- package/dist/types/src/vite-plugin/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/manifest.d.ts +41 -0
- package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/manifest.test.d.ts +2 -0
- package/dist/types/src/vite-plugin/manifest.test.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/packages.d.ts +10 -4
- package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/moon.yml +1 -0
- package/package.json +36 -60
- package/src/cli/cli.ts +1 -1
- package/src/common/annotations.ts +3 -0
- package/src/common/capabilities.ts +2 -1
- package/src/common/operations.ts +1 -1
- package/src/core/capability.ts +1 -1
- package/src/core/edge-registry-plugin-provider.ts +92 -0
- package/src/core/index.ts +5 -0
- package/src/core/plugin-asset-cache.ts +60 -0
- package/src/core/plugin-manager.test.ts +793 -7
- package/src/core/plugin-manager.ts +846 -44
- package/src/core/plugin-manifest.test.ts +75 -0
- package/src/core/plugin-manifest.ts +134 -0
- package/src/core/plugin.ts +170 -10
- package/src/core/registry.ts +163 -0
- package/src/core/url-loader.test.ts +148 -6
- package/src/core/url-loader.ts +284 -44
- package/src/plugin-operation/OperationPlugin.ts +2 -2
- package/src/plugin-operation/history/capability.ts +1 -1
- package/src/plugin-operation/history/history-tracker.test.ts +2 -1
- package/src/plugin-operation/history/history-tracker.ts +1 -1
- package/src/plugin-operation/history/types.ts +1 -1
- package/src/plugin-operation/history/undo-mapping.ts +1 -1
- package/src/plugin-operation/history/undo-registry.ts +1 -1
- package/src/plugin-operation/invoker-capability.ts +2 -1
- package/src/plugin-operation/meta.ts +1 -0
- package/src/plugin-operation/testing.ts +2 -1
- package/src/plugin-runtime/RuntimePlugin.ts +2 -2
- package/src/plugin-runtime/meta.ts +1 -0
- package/src/testing/harness.ts +229 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/react.test.tsx +48 -0
- package/src/testing/react.tsx +113 -0
- package/src/testing/service.ts +1 -1
- package/src/testing/withPluginManager.stories.tsx +1 -1
- package/src/testing/withPluginManager.tsx +0 -2
- package/src/ui/components/App/App.stories.tsx +2 -6
- package/src/ui/components/App/App.tsx +25 -2
- package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
- package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
- package/src/ui/components/Placeholder/index.ts +5 -0
- package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +4 -2
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +1 -1
- package/src/ui/components/Surface/SurfaceComponent.tsx +85 -48
- package/src/ui/components/Surface/SurfaceInfo.tsx +0 -1
- package/src/ui/components/Surface/index.ts +20 -1
- package/src/ui/components/Surface/types.test.ts +126 -0
- package/src/ui/components/Surface/types.ts +164 -12
- package/src/ui/components/index.ts +1 -0
- package/src/ui/hooks/useApp.test.tsx +1 -1
- package/src/ui/hooks/useApp.tsx +177 -42
- package/src/ui/hooks/useLoading.tsx +14 -6
- package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +270 -0
- package/src/vite-plugin/boot-loader/boot-loader.css +321 -0
- package/src/vite-plugin/boot-loader/boot-loader.js +325 -0
- package/src/vite-plugin/boot-loader/index.ts +5 -0
- package/src/vite-plugin/boot-loader/loader.ts +123 -0
- package/src/vite-plugin/composer/index.ts +306 -0
- package/src/vite-plugin/import-map/index.ts +527 -0
- package/src/vite-plugin/index.ts +6 -2
- package/src/vite-plugin/manifest.test.ts +46 -0
- package/src/vite-plugin/manifest.ts +57 -0
- package/src/vite-plugin/packages.ts +168 -10
- package/tsconfig.json +16 -1
- package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
- package/dist/lib/browser/capability-OP63CD5N.mjs.map +0 -7
- package/dist/lib/browser/chunk-F7FW2RK2.mjs.map +0 -7
- package/dist/lib/browser/chunk-FU4GAFUQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-GX4TUNM6.mjs +0 -963
- package/dist/lib/browser/chunk-GX4TUNM6.mjs.map +0 -7
- package/dist/lib/browser/chunk-JKWMHZP6.mjs.map +0 -7
- package/dist/lib/browser/chunk-LVJW5EFU.mjs +0 -157
- package/dist/lib/browser/chunk-LVJW5EFU.mjs.map +0 -7
- package/dist/lib/browser/chunk-RFSO3JRG.mjs +0 -1
- package/dist/lib/browser/chunk-RFSO3JRG.mjs.map +0 -7
- package/dist/lib/browser/chunk-WPE6AL7I.mjs +0 -905
- package/dist/lib/browser/chunk-WPE6AL7I.mjs.map +0 -7
- package/dist/lib/browser/invoker-capability-H5PPENOC.mjs.map +0 -7
- package/dist/lib/node-esm/capability-WFEG6CIZ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-4A3ZCMI3.mjs +0 -158
- package/dist/lib/node-esm/chunk-4A3ZCMI3.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-BCEOLX47.mjs +0 -964
- package/dist/lib/node-esm/chunk-BCEOLX47.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-EL3R25OQ.mjs +0 -2
- package/dist/lib/node-esm/chunk-EL3R25OQ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-G3RTFSNG.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-LQKOTNJW.mjs +0 -906
- package/dist/lib/node-esm/chunk-LQKOTNJW.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ULUEXB7Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-URWHJQT2.mjs.map +0 -7
- package/dist/lib/node-esm/invoker-capability-S3ZA527J.mjs.map +0 -7
- package/dist/types/src/vite-plugin/composer-plugin.d.ts +0 -18
- package/dist/types/src/vite-plugin/composer-plugin.d.ts.map +0 -1
- package/dist/types/src/vite-plugin/import-map-plugin.d.ts +0 -16
- package/dist/types/src/vite-plugin/import-map-plugin.d.ts.map +0 -1
- package/src/vite-plugin/composer-plugin.ts +0 -128
- package/src/vite-plugin/import-map-plugin.ts +0 -314
- /package/dist/lib/browser/{capability-BBBBAPDI.mjs.map → capability-VQ236TY5.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-I34GF4NG.mjs.map → chunk-45CHLTBV.mjs.map} +0 -0
- /package/dist/lib/node-esm/{capability-AWBEMRYR.mjs.map → capability-GS3XJH74.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs.map → chunk-6XW6LET6.mjs.map} +0 -0
|
@@ -5,19 +5,133 @@
|
|
|
5
5
|
import { assert, describe, it } from '@effect/vitest';
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
|
|
8
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
9
|
+
|
|
8
10
|
import * as Plugin from './plugin';
|
|
11
|
+
import * as PluginAssetCache from './plugin-asset-cache';
|
|
9
12
|
import * as UrlLoader from './url-loader';
|
|
10
13
|
|
|
11
14
|
const testMeta = { id: 'org.dxos.plugin.test', name: 'Test' };
|
|
12
15
|
|
|
16
|
+
const memoryStorage = (initial: string | null = null): UrlLoader.Storage => {
|
|
17
|
+
let value = initial;
|
|
18
|
+
return {
|
|
19
|
+
get: () => value,
|
|
20
|
+
set: (_key, next) => {
|
|
21
|
+
value = next;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type CacheCall = { method: 'cache' | 'evict' | 'resolve'; pluginId: string; urls?: readonly string[]; url?: string };
|
|
27
|
+
|
|
28
|
+
const recordingCache = (): { cache: PluginAssetCache.Cache; calls: CacheCall[] } => {
|
|
29
|
+
const calls: CacheCall[] = [];
|
|
30
|
+
return {
|
|
31
|
+
calls,
|
|
32
|
+
cache: {
|
|
33
|
+
cache: (pluginId, urls) =>
|
|
34
|
+
Effect.sync(() => {
|
|
35
|
+
calls.push({ method: 'cache', pluginId, urls });
|
|
36
|
+
}),
|
|
37
|
+
evict: (pluginId) =>
|
|
38
|
+
Effect.sync(() => {
|
|
39
|
+
calls.push({ method: 'evict', pluginId });
|
|
40
|
+
}),
|
|
41
|
+
resolve: (pluginId, url) =>
|
|
42
|
+
Effect.sync(() => {
|
|
43
|
+
calls.push({ method: 'resolve', pluginId, url });
|
|
44
|
+
return url;
|
|
45
|
+
}),
|
|
46
|
+
list: () => Effect.succeed([] as readonly string[]),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
13
51
|
describe('UrlLoader', () => {
|
|
52
|
+
describe('isLocalUrl', () => {
|
|
53
|
+
it('matches localhost, 127.0.0.1, and ::1', ({ expect }) => {
|
|
54
|
+
expect(UrlLoader.isLocalUrl('http://localhost:5173/plugin.mjs')).toBe(true);
|
|
55
|
+
expect(UrlLoader.isLocalUrl('https://LOCALHOST/plugin.mjs')).toBe(true);
|
|
56
|
+
expect(UrlLoader.isLocalUrl('http://127.0.0.1:8080/plugin.mjs')).toBe(true);
|
|
57
|
+
expect(UrlLoader.isLocalUrl('http://[::1]:8080/plugin.mjs')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects public and malformed URLs', ({ expect }) => {
|
|
61
|
+
expect(UrlLoader.isLocalUrl('https://example.com/plugin.mjs')).toBe(false);
|
|
62
|
+
expect(UrlLoader.isLocalUrl('https://192.168.1.10/plugin.mjs')).toBe(false);
|
|
63
|
+
expect(UrlLoader.isLocalUrl('not a url')).toBe(false);
|
|
64
|
+
expect(UrlLoader.isLocalUrl('')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('getRemoteEntries', () => {
|
|
69
|
+
it('returns persisted entries from storage', ({ expect }) => {
|
|
70
|
+
const storage: UrlLoader.Storage = {
|
|
71
|
+
get: () => '[{"id":"p1","url":"http://localhost:5173/p.mjs"}]',
|
|
72
|
+
set: () => {},
|
|
73
|
+
};
|
|
74
|
+
expect(UrlLoader.getRemoteEntries({ storage })).toEqual([{ id: 'p1', url: 'http://localhost:5173/p.mjs' }]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns an empty array when storage is empty or malformed', ({ expect }) => {
|
|
78
|
+
const empty: UrlLoader.Storage = { get: () => null, set: () => {} };
|
|
79
|
+
const malformed: UrlLoader.Storage = { get: () => '{not json', set: () => {} };
|
|
80
|
+
expect(UrlLoader.getRemoteEntries({ storage: empty })).toEqual([]);
|
|
81
|
+
expect(UrlLoader.getRemoteEntries({ storage: malformed })).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('setInstalledVersion / getInstalledVersion', () => {
|
|
86
|
+
it('stores and retrieves an installed version', ({ expect }) => {
|
|
87
|
+
const stored: Record<string, string> = {};
|
|
88
|
+
const storage: UrlLoader.Storage = {
|
|
89
|
+
get: (key) => stored[key] ?? null,
|
|
90
|
+
set: (key, value) => {
|
|
91
|
+
stored[key] = value;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Seed a remote entry without a version.
|
|
96
|
+
storage.set('test-key', JSON.stringify([{ id: 'org.dxos.plugin.foo', url: 'http://example.com/p.mjs' }]));
|
|
97
|
+
|
|
98
|
+
UrlLoader.setInstalledVersion('org.dxos.plugin.foo', 'v1.0.0', { storage, key: 'test-key' });
|
|
99
|
+
|
|
100
|
+
expect(UrlLoader.getInstalledVersion('org.dxos.plugin.foo', { storage, key: 'test-key' })).toBe('v1.0.0');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns undefined when plugin is not persisted', ({ expect }) => {
|
|
104
|
+
const storage: UrlLoader.Storage = { get: () => null, set: () => {} };
|
|
105
|
+
expect(UrlLoader.getInstalledVersion('org.dxos.plugin.missing', { storage })).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('overwrites an existing version when called again', ({ expect }) => {
|
|
109
|
+
const stored: Record<string, string> = {};
|
|
110
|
+
const storage: UrlLoader.Storage = {
|
|
111
|
+
get: (key) => stored[key] ?? null,
|
|
112
|
+
set: (key, value) => {
|
|
113
|
+
stored[key] = value;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
storage.set(
|
|
117
|
+
'test-key',
|
|
118
|
+
JSON.stringify([{ id: 'org.dxos.plugin.foo', url: 'http://example.com/p.mjs', version: 'v1.0.0' }]),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
UrlLoader.setInstalledVersion('org.dxos.plugin.foo', 'v2.0.0', { storage, key: 'test-key' });
|
|
122
|
+
expect(UrlLoader.getInstalledVersion('org.dxos.plugin.foo', { storage, key: 'test-key' })).toBe('v2.0.0');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
14
126
|
describe('make', () => {
|
|
15
127
|
it.effect('resolves built-in plugins by meta.id', () =>
|
|
16
128
|
Effect.gen(function* () {
|
|
17
129
|
const testPlugin = Plugin.make(Plugin.define(testMeta))();
|
|
18
130
|
const loader = UrlLoader.make([testPlugin]);
|
|
19
131
|
const result = yield* loader(testMeta.id);
|
|
20
|
-
assert.strictEqual(result.meta.id, testMeta.id);
|
|
132
|
+
assert.strictEqual(result.plugin.meta.id, testMeta.id);
|
|
133
|
+
// Builtins are not dev plugins.
|
|
134
|
+
assert.notOk(result.dev);
|
|
21
135
|
}),
|
|
22
136
|
);
|
|
23
137
|
|
|
@@ -36,7 +150,7 @@ describe('UrlLoader', () => {
|
|
|
36
150
|
get: () => null,
|
|
37
151
|
set: () => {},
|
|
38
152
|
};
|
|
39
|
-
const result = await UrlLoader.preload({ storage });
|
|
153
|
+
const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
|
|
40
154
|
expect(result).toEqual([]);
|
|
41
155
|
});
|
|
42
156
|
|
|
@@ -45,7 +159,7 @@ describe('UrlLoader', () => {
|
|
|
45
159
|
get: () => '{{invalid json',
|
|
46
160
|
set: () => {},
|
|
47
161
|
};
|
|
48
|
-
const result = await UrlLoader.preload({ storage });
|
|
162
|
+
const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
|
|
49
163
|
expect(result).toEqual([]);
|
|
50
164
|
});
|
|
51
165
|
|
|
@@ -54,7 +168,7 @@ describe('UrlLoader', () => {
|
|
|
54
168
|
get: () => 'null',
|
|
55
169
|
set: () => {},
|
|
56
170
|
};
|
|
57
|
-
const result = await UrlLoader.preload({ storage });
|
|
171
|
+
const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
|
|
58
172
|
expect(result).toEqual([]);
|
|
59
173
|
});
|
|
60
174
|
|
|
@@ -63,7 +177,7 @@ describe('UrlLoader', () => {
|
|
|
63
177
|
get: () => '{}',
|
|
64
178
|
set: () => {},
|
|
65
179
|
};
|
|
66
|
-
const result = await UrlLoader.preload({ storage });
|
|
180
|
+
const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
|
|
67
181
|
expect(result).toEqual([]);
|
|
68
182
|
});
|
|
69
183
|
|
|
@@ -72,8 +186,36 @@ describe('UrlLoader', () => {
|
|
|
72
186
|
get: () => '[{"title":"no url"}]',
|
|
73
187
|
set: () => {},
|
|
74
188
|
};
|
|
75
|
-
const result = await UrlLoader.preload({ storage });
|
|
189
|
+
const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
|
|
76
190
|
expect(result).toEqual([]);
|
|
77
191
|
});
|
|
78
192
|
});
|
|
193
|
+
|
|
194
|
+
describe('uninstall', () => {
|
|
195
|
+
it('removes the persisted entry and evicts cached assets', async ({ expect }) => {
|
|
196
|
+
const storage = memoryStorage('[{"id":"p1","url":"https://x/p1.json"},{"id":"p2","url":"https://x/p2.json"}]');
|
|
197
|
+
const { cache, calls } = recordingCache();
|
|
198
|
+
await runAndForwardErrors(UrlLoader.uninstall('p1', { storage, cache }));
|
|
199
|
+
expect(UrlLoader.getRemoteEntries({ storage })).toEqual([{ id: 'p2', url: 'https://x/p2.json' }]);
|
|
200
|
+
expect(calls).toEqual([{ method: 'evict', pluginId: 'p1' }]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('still removes entry when cache eviction fails', async ({ expect }) => {
|
|
204
|
+
const storage = memoryStorage('[{"id":"p1","url":"https://x/p1.json"}]');
|
|
205
|
+
const cache: PluginAssetCache.Cache = {
|
|
206
|
+
cache: () => Effect.void,
|
|
207
|
+
evict: () =>
|
|
208
|
+
Effect.fail(
|
|
209
|
+
new PluginAssetCache.PluginAssetCacheError({
|
|
210
|
+
context: { operation: 'evict', pluginId: 'p1' },
|
|
211
|
+
cause: 'boom',
|
|
212
|
+
}),
|
|
213
|
+
),
|
|
214
|
+
resolve: (_id, url) => Effect.succeed(url),
|
|
215
|
+
list: () => Effect.succeed([] as readonly string[]),
|
|
216
|
+
};
|
|
217
|
+
await runAndForwardErrors(UrlLoader.uninstall('p1', { storage, cache }));
|
|
218
|
+
expect(UrlLoader.getRemoteEntries({ storage })).toEqual([]);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
79
221
|
});
|
package/src/core/url-loader.ts
CHANGED
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Option from 'effect/Option';
|
|
6
7
|
|
|
8
|
+
import { BaseError } from '@dxos/errors';
|
|
7
9
|
import { log } from '@dxos/log';
|
|
8
10
|
|
|
9
11
|
import * as Plugin from './plugin';
|
|
12
|
+
import * as PluginAssetCache from './plugin-asset-cache';
|
|
13
|
+
import * as PluginManifest from './plugin-manifest';
|
|
10
14
|
|
|
11
15
|
const DEFAULT_KEY = 'org.dxos.composer.remote-plugins';
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Tagged error for any failure during remote plugin loading. Construction sites
|
|
19
|
+
* set `context.locator` and `context.reason` (one of `'invalid-locator' |
|
|
20
|
+
* 'manifest-error' | 'cache-error' | 'import-failed' | 'meta-missing' |
|
|
21
|
+
* 'meta-mismatch' | 'duplicate-id'`) so handlers can route on the failure mode.
|
|
22
|
+
*/
|
|
23
|
+
export class RemotePluginLoadError extends BaseError.extend('RemotePluginLoadError', 'Failed to load remote plugin') {}
|
|
24
|
+
|
|
13
25
|
/**
|
|
14
26
|
* Abstraction over key-value storage (defaults to localStorage).
|
|
15
27
|
*/
|
|
@@ -24,9 +36,35 @@ export type Storage = {
|
|
|
24
36
|
export type Options = {
|
|
25
37
|
storage?: Storage;
|
|
26
38
|
key?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Per-platform offline asset cache. Defaults to a no-op cache (no offline support).
|
|
41
|
+
*/
|
|
42
|
+
cache?: PluginAssetCache.Cache;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options for the preload entry point. Adds an optional progress hook so hosts
|
|
47
|
+
* can drive a boot-loader counter (`Loading plugins (3/12)…`) as remote plugin
|
|
48
|
+
* manifests resolve. Resolution order is *not* deterministic — each entry races
|
|
49
|
+
* its own network fetch — so callers should treat `loaded` as a count, not an
|
|
50
|
+
* index. Failures are still swallowed; `loaded` advances on both success and
|
|
51
|
+
* recoverable failure so the counter always reaches `total`.
|
|
52
|
+
*/
|
|
53
|
+
export type PreloadOptions = Options & {
|
|
54
|
+
onPluginLoaded?: (loaded: number, total: number) => void;
|
|
27
55
|
};
|
|
28
56
|
|
|
29
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Persisted record of a remote plugin that has been loaded previously.
|
|
59
|
+
*
|
|
60
|
+
* `url` is the URL of the plugin manifest (`plugin.json`), not the entry module.
|
|
61
|
+
*/
|
|
62
|
+
export type RemotePluginEntry = {
|
|
63
|
+
id: string;
|
|
64
|
+
url: string;
|
|
65
|
+
/** Installed version string, e.g. `v1.0.0`. Populated after install from a community catalog entry. */
|
|
66
|
+
version?: string;
|
|
67
|
+
};
|
|
30
68
|
|
|
31
69
|
const defaultStorage = (): Storage => ({
|
|
32
70
|
get: (key) => localStorage.getItem(key),
|
|
@@ -58,6 +96,15 @@ const persistRemotePlugin = (storage: Storage, key: string, entry: RemotePluginE
|
|
|
58
96
|
}
|
|
59
97
|
};
|
|
60
98
|
|
|
99
|
+
const removePersistedRemotePlugin = (storage: Storage, key: string, pluginId: string): void => {
|
|
100
|
+
try {
|
|
101
|
+
const entries = getPersistedRemotePlugins(storage, key).filter((existing) => existing.id !== pluginId);
|
|
102
|
+
storage.set(key, JSON.stringify(entries));
|
|
103
|
+
} catch (error) {
|
|
104
|
+
log.warn('failed to remove remote plugin entry', { pluginId, error });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
61
108
|
const isUrl = (locator: string): boolean => {
|
|
62
109
|
try {
|
|
63
110
|
const url = new URL(locator);
|
|
@@ -67,6 +114,60 @@ const isUrl = (locator: string): boolean => {
|
|
|
67
114
|
}
|
|
68
115
|
};
|
|
69
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Returns true when the URL's hostname is the local host (localhost, 127.0.0.1, or ::1).
|
|
119
|
+
*/
|
|
120
|
+
export const isLocalUrl = (locator: string): boolean => {
|
|
121
|
+
try {
|
|
122
|
+
const hostname = new URL(locator).hostname.toLowerCase();
|
|
123
|
+
// WHATWG URL returns '::1' without brackets in Node; some browsers return '[::1]'.
|
|
124
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the list of remote plugin entries previously persisted by {@link make}.
|
|
132
|
+
* Useful for UI code that needs to know which loaded plugins were installed from a URL
|
|
133
|
+
* (e.g. to surface a tag on remote or localhost-hosted plugins).
|
|
134
|
+
*/
|
|
135
|
+
export const getRemoteEntries = (options: Options = {}): readonly RemotePluginEntry[] => {
|
|
136
|
+
const storage = options.storage ?? defaultStorage();
|
|
137
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
138
|
+
return getPersistedRemotePlugins(storage, key);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Updates the persisted installed version for an already-stored remote plugin entry.
|
|
143
|
+
* Does nothing if the plugin has not been persisted yet.
|
|
144
|
+
*/
|
|
145
|
+
export const setInstalledVersion = (id: string, version: string, options: Options = {}): void => {
|
|
146
|
+
const storage = options.storage ?? defaultStorage();
|
|
147
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
148
|
+
const entries = getPersistedRemotePlugins(storage, key);
|
|
149
|
+
const index = entries.findIndex((entry) => entry.id === id);
|
|
150
|
+
if (index === -1) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
entries[index] = { ...entries[index], version };
|
|
154
|
+
try {
|
|
155
|
+
storage.set(key, JSON.stringify(entries));
|
|
156
|
+
} catch (error) {
|
|
157
|
+
log.warn('failed to update installed version for remote plugin', { id, version, error });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Returns the installed version string for the given plugin id, or `undefined` if
|
|
163
|
+
* the plugin was installed without version tracking (legacy install) or is not found.
|
|
164
|
+
*/
|
|
165
|
+
export const getInstalledVersion = (id: string, options: Options = {}): string | undefined => {
|
|
166
|
+
const storage = options.storage ?? defaultStorage();
|
|
167
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
168
|
+
return getPersistedRemotePlugins(storage, key).find((entry) => entry.id === id)?.version;
|
|
169
|
+
};
|
|
170
|
+
|
|
70
171
|
const normalizePluginExport = (mod: Record<string, unknown>): Plugin.Plugin => {
|
|
71
172
|
const exported = mod.default;
|
|
72
173
|
if (Plugin.isPlugin(exported)) {
|
|
@@ -81,68 +182,207 @@ const normalizePluginExport = (mod: Record<string, unknown>): Plugin.Plugin => {
|
|
|
81
182
|
throw new Error('Remote module default export is not a Plugin or a zero-arg plugin factory.');
|
|
82
183
|
};
|
|
83
184
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Loads stylesheets declared in the manifest by appending `<link rel="stylesheet">` elements to the host document.
|
|
187
|
+
* Each link is tagged with `data-dxos-plugin-id` so `uninstall` can clean them up.
|
|
188
|
+
*/
|
|
189
|
+
const loadStylesheets = (
|
|
190
|
+
manifest: PluginManifest.ResolvedManifest,
|
|
191
|
+
cache: PluginAssetCache.Cache,
|
|
192
|
+
): Effect.Effect<void, PluginAssetCache.PluginAssetCacheError> =>
|
|
193
|
+
Effect.gen(function* () {
|
|
194
|
+
if (typeof document === 'undefined') {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const cssUrls = manifest.assetUrls.filter((url) => url.endsWith('.css'));
|
|
198
|
+
for (const url of cssUrls) {
|
|
199
|
+
const resolved = yield* cache.resolve(manifest.id, url);
|
|
200
|
+
if (document.querySelector(`link[data-dxos-plugin-id="${manifest.id}"][href="${resolved}"]`)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const link = document.createElement('link');
|
|
204
|
+
link.rel = 'stylesheet';
|
|
205
|
+
link.href = resolved;
|
|
206
|
+
link.dataset.dxosPluginId = manifest.id;
|
|
207
|
+
document.head.appendChild(link);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const loadFromManifest = (
|
|
212
|
+
manifestUrl: string,
|
|
213
|
+
cache: PluginAssetCache.Cache,
|
|
214
|
+
): Effect.Effect<{ plugin: Plugin.Plugin; manifest: PluginManifest.ResolvedManifest }, RemotePluginLoadError> =>
|
|
215
|
+
Effect.gen(function* () {
|
|
216
|
+
log.info('loading remote plugin', { manifestUrl });
|
|
217
|
+
const manifest = yield* PluginManifest.fetchManifest(manifestUrl).pipe(
|
|
218
|
+
Effect.mapError(
|
|
219
|
+
(cause) => new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'manifest-error' }, cause }),
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
const wrapCacheError = Effect.mapError(
|
|
223
|
+
(cause: PluginAssetCache.PluginAssetCacheError) =>
|
|
224
|
+
new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'cache-error' }, cause }),
|
|
225
|
+
);
|
|
226
|
+
// Dev-mode manifests (served by `vite dev` via composerPlugin) skip the offline
|
|
227
|
+
// cache: chunks resolve on demand through the dev server, the asset list isn't
|
|
228
|
+
// enumerable up-front, and CSS arrives via runtime <style> injection rather than
|
|
229
|
+
// static <link> tags.
|
|
230
|
+
if (!manifest.dev) {
|
|
231
|
+
// Cache the manifest URL alongside its declared assets. Without it, `preload` on a
|
|
232
|
+
// subsequent reload would fetch the manifest from the network — and fail when the
|
|
233
|
+
// plugin's host is offline, dropping the plugin from the runtime.
|
|
234
|
+
const cachedUrls =
|
|
235
|
+
manifest.assetUrls.indexOf(manifestUrl) === -1 ? [manifestUrl, ...manifest.assetUrls] : manifest.assetUrls;
|
|
236
|
+
yield* cache.cache(manifest.id, cachedUrls).pipe(wrapCacheError);
|
|
237
|
+
}
|
|
238
|
+
const entryUrl = manifest.dev
|
|
239
|
+
? manifest.entryUrl
|
|
240
|
+
: yield* cache.resolve(manifest.id, manifest.entryUrl).pipe(wrapCacheError);
|
|
241
|
+
const mod = yield* Effect.tryPromise({
|
|
242
|
+
try: () => import(/* @vite-ignore */ entryUrl),
|
|
243
|
+
catch: (cause) =>
|
|
244
|
+
new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'import-failed' }, cause }),
|
|
245
|
+
});
|
|
246
|
+
const plugin = normalizePluginExport(mod);
|
|
247
|
+
if (!plugin.meta.id || !plugin.meta.name) {
|
|
248
|
+
return yield* Effect.fail(
|
|
249
|
+
new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'meta-missing' } }),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (plugin.meta.id !== manifest.id) {
|
|
253
|
+
return yield* Effect.fail(
|
|
254
|
+
new RemotePluginLoadError({
|
|
255
|
+
context: {
|
|
256
|
+
locator: manifestUrl,
|
|
257
|
+
reason: 'meta-mismatch',
|
|
258
|
+
metaId: plugin.meta.id,
|
|
259
|
+
manifestId: manifest.id,
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
// Append stylesheets only after the entry imports cleanly and meta validation
|
|
265
|
+
// passes. If we did this earlier and the import or meta checks failed, the
|
|
266
|
+
// `<link>` tags would leak into the host DOM with no plugin to own their
|
|
267
|
+
// teardown — `uninstall` only runs for plugins the manager actually accepted.
|
|
268
|
+
// In dev mode, Vite injects styles at runtime via the module graph, so static
|
|
269
|
+
// <link> tags would duplicate (or 404, since CSS chunks aren't pre-built).
|
|
270
|
+
if (!manifest.dev) {
|
|
271
|
+
yield* loadStylesheets(manifest, cache).pipe(wrapCacheError);
|
|
272
|
+
}
|
|
273
|
+
return { plugin, manifest };
|
|
274
|
+
});
|
|
93
275
|
|
|
94
276
|
/**
|
|
95
|
-
* Preloads previously persisted remote plugins from storage.
|
|
277
|
+
* Preloads previously persisted remote plugins from storage. Per-entry failures
|
|
278
|
+
* are logged and swallowed — the returned effect always succeeds with whichever
|
|
279
|
+
* plugins loaded cleanly, so a single bad entry can't block the host's startup.
|
|
96
280
|
*/
|
|
97
|
-
export const preload =
|
|
98
|
-
|
|
99
|
-
|
|
281
|
+
export const preload = (options: PreloadOptions = {}): Effect.Effect<Plugin.Plugin[], never> =>
|
|
282
|
+
Effect.gen(function* () {
|
|
283
|
+
const storage = options.storage ?? defaultStorage();
|
|
284
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
285
|
+
const cache = options.cache ?? PluginAssetCache.noop();
|
|
286
|
+
const onPluginLoaded = options.onPluginLoaded;
|
|
100
287
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
log.info('preloading remote plugins', { count: entries.length });
|
|
106
|
-
const results = await Promise.allSettled(entries.map((entry) => loadRemotePlugin(entry.url)));
|
|
107
|
-
const plugins: Plugin.Plugin[] = [];
|
|
108
|
-
for (let index = 0; index < results.length; index++) {
|
|
109
|
-
const result = results[index];
|
|
110
|
-
if (result.status === 'fulfilled') {
|
|
111
|
-
plugins.push(result.value);
|
|
112
|
-
} else {
|
|
113
|
-
log.warn('failed to preload remote plugin', { entry: entries[index], error: result.reason });
|
|
288
|
+
const entries = getPersistedRemotePlugins(storage, key);
|
|
289
|
+
if (entries.length === 0) {
|
|
290
|
+
return [];
|
|
114
291
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
292
|
+
log.info('preloading remote plugins', { count: entries.length });
|
|
293
|
+
const total = entries.length;
|
|
294
|
+
let loaded = 0;
|
|
295
|
+
const results = yield* Effect.all(
|
|
296
|
+
entries.map((entry) =>
|
|
297
|
+
loadFromManifest(entry.url, cache).pipe(
|
|
298
|
+
Effect.tapError((error) => Effect.sync(() => log.warn('failed to preload remote plugin', { entry, error }))),
|
|
299
|
+
Effect.option,
|
|
300
|
+
// Tick the progress hook on both success and recoverable failure so
|
|
301
|
+
// the counter monotonically reaches `total`. The host-supplied
|
|
302
|
+
// callback is best-effort: any synchronous throw flows through
|
|
303
|
+
// `Effect.try` and gets logged + ignored so a buggy hook can't
|
|
304
|
+
// derail the preload.
|
|
305
|
+
Effect.tap(() =>
|
|
306
|
+
Effect.sync(() => {
|
|
307
|
+
loaded += 1;
|
|
308
|
+
}).pipe(
|
|
309
|
+
Effect.andThen(Effect.try(() => onPluginLoaded?.(loaded, total))),
|
|
310
|
+
Effect.tapError((error) => Effect.sync(() => log.warn('onPluginLoaded threw', { loaded, total, error }))),
|
|
311
|
+
Effect.ignore,
|
|
312
|
+
),
|
|
313
|
+
),
|
|
314
|
+
),
|
|
315
|
+
),
|
|
316
|
+
{ concurrency: 'unbounded' },
|
|
317
|
+
);
|
|
318
|
+
return results.flatMap((result) =>
|
|
319
|
+
Option.match(result, {
|
|
320
|
+
onNone: () => [],
|
|
321
|
+
onSome: ({ plugin }) => [plugin],
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
});
|
|
118
325
|
|
|
119
326
|
/**
|
|
120
327
|
* Creates a plugin loader that resolves built-in plugins by ID or loads remote plugins from URLs.
|
|
328
|
+
*
|
|
329
|
+
* Remote URLs must point at a plugin manifest (`plugin.json`). The loader fetches the manifest,
|
|
330
|
+
* eagerly persists every declared asset via the configured `PluginAssetCache`, then dynamic-imports
|
|
331
|
+
* the entry module.
|
|
121
332
|
*/
|
|
122
333
|
export const make = (builtinPlugins: Plugin.Plugin[], options: Options = {}) => {
|
|
123
334
|
const storage = options.storage ?? defaultStorage();
|
|
124
335
|
const key = options.key ?? DEFAULT_KEY;
|
|
336
|
+
const cache = options.cache ?? PluginAssetCache.noop();
|
|
125
337
|
|
|
126
|
-
return (locator: string): Effect.Effect<Plugin.Plugin,
|
|
338
|
+
return (locator: string): Effect.Effect<{ plugin: Plugin.Plugin; dev?: boolean }, RemotePluginLoadError> =>
|
|
127
339
|
Effect.gen(function* () {
|
|
128
340
|
const builtin = builtinPlugins.find((plugin) => plugin.meta.id === locator);
|
|
129
341
|
if (builtin) {
|
|
130
|
-
return builtin;
|
|
342
|
+
return { plugin: builtin };
|
|
131
343
|
}
|
|
132
344
|
if (!isUrl(locator)) {
|
|
133
|
-
return yield* Effect.fail(new
|
|
345
|
+
return yield* Effect.fail(new RemotePluginLoadError({ context: { locator, reason: 'invalid-locator' } }));
|
|
134
346
|
}
|
|
135
|
-
const plugin = yield*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
347
|
+
const { plugin, manifest } = yield* loadFromManifest(locator, cache);
|
|
348
|
+
// Production manifests can't collide with a builtin id — that would make
|
|
349
|
+
// the registered plugin permanently inaccessible. Dev manifests are
|
|
350
|
+
// allowed to collide because the manager shadow primitive handles the
|
|
351
|
+
// overlap (dev plugin takes the id slot for the session, original is
|
|
352
|
+
// restored on uninstall or page reload).
|
|
353
|
+
if (!manifest.dev) {
|
|
354
|
+
const duplicate = builtinPlugins.find((existing) => existing.meta.id === plugin.meta.id);
|
|
355
|
+
if (duplicate) {
|
|
356
|
+
return yield* Effect.fail(
|
|
357
|
+
new RemotePluginLoadError({ context: { locator, reason: 'duplicate-id', id: plugin.meta.id } }),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
persistRemotePlugin(storage, key, { id: plugin.meta.id, url: locator });
|
|
144
361
|
}
|
|
145
|
-
|
|
146
|
-
return plugin;
|
|
362
|
+
return { plugin, dev: manifest.dev };
|
|
147
363
|
});
|
|
148
364
|
};
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Removes a previously installed remote plugin: drops the persisted entry, evicts cached
|
|
368
|
+
* assets, and removes any stylesheet `<link>` tags that {@link loadFromManifest} appended.
|
|
369
|
+
*
|
|
370
|
+
* Cache eviction failures are logged and swallowed — the persisted entry has already been
|
|
371
|
+
* dropped so the user-visible state is consistent regardless of whether the platform
|
|
372
|
+
* cache cooperated.
|
|
373
|
+
*/
|
|
374
|
+
export const uninstall = (pluginId: string, options: Options = {}): Effect.Effect<void, never> =>
|
|
375
|
+
Effect.gen(function* () {
|
|
376
|
+
const storage = options.storage ?? defaultStorage();
|
|
377
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
378
|
+
const cache = options.cache ?? PluginAssetCache.noop();
|
|
379
|
+
|
|
380
|
+
removePersistedRemotePlugin(storage, key, pluginId);
|
|
381
|
+
if (typeof document !== 'undefined') {
|
|
382
|
+
document.querySelectorAll(`link[data-dxos-plugin-id="${pluginId}"]`).forEach((node) => node.remove());
|
|
383
|
+
}
|
|
384
|
+
yield* cache.evict(pluginId).pipe(
|
|
385
|
+
Effect.tapError((error) => Effect.sync(() => log.warn('failed to evict plugin assets', { pluginId, error }))),
|
|
386
|
+
Effect.ignore,
|
|
387
|
+
);
|
|
388
|
+
});
|
|
@@ -12,8 +12,8 @@ const HistoryCapabilities = Capability.lazy('HistoryCapabilities', () => import(
|
|
|
12
12
|
export const OperationPlugin = Plugin.define(meta).pipe(
|
|
13
13
|
Plugin.addModule({
|
|
14
14
|
activatesOn: ActivationEvents.ManagedRuntimeReady,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
firesBeforeActivation: [ActivationEvents.SetupOperationHandler],
|
|
16
|
+
firesAfterActivation: [ActivationEvents.OperationInvokerReady],
|
|
17
17
|
activate: OperationInvoker,
|
|
18
18
|
}),
|
|
19
19
|
Plugin.addModule({
|
|
@@ -10,7 +10,8 @@ import * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
|
10
10
|
import * as TestClock from 'effect/TestClock';
|
|
11
11
|
import { describe, expect } from 'vitest';
|
|
12
12
|
|
|
13
|
-
import { Operation
|
|
13
|
+
import { Operation } from '@dxos/compute';
|
|
14
|
+
import { OperationInvoker } from '@dxos/operation';
|
|
14
15
|
|
|
15
16
|
const testRuntime = ManagedRuntime.make(Layer.empty) as unknown as ManagedRuntime.ManagedRuntime<any, any>;
|
|
16
17
|
|
|
@@ -7,7 +7,7 @@ import * as Stream from 'effect/Stream';
|
|
|
7
7
|
|
|
8
8
|
import { runAndForwardErrors } from '@dxos/effect';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
|
-
import {
|
|
10
|
+
import { OperationInvoker } from '@dxos/operation';
|
|
11
11
|
|
|
12
12
|
import { UndoOperation } from '../../common';
|
|
13
13
|
import { EmptyHistoryError } from './errors';
|