@dxos/app-framework 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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/dist/lib/browser/{capability-BBBBAPDI.mjs → capability-Q5XRXRD2.mjs} +10 -10
- package/dist/lib/browser/{capability-OP63CD5N.mjs → capability-V7LR4LQN.mjs} +11 -11
- package/dist/lib/browser/capability-V7LR4LQN.mjs.map +7 -0
- 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-GX4TUNM6.mjs → chunk-3ZS2A3DN.mjs} +170 -226
- package/dist/lib/browser/chunk-3ZS2A3DN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-I34GF4NG.mjs → chunk-45CHLTBV.mjs} +2 -2
- package/dist/lib/browser/chunk-5LAIGWLU.mjs +467 -0
- package/dist/lib/browser/chunk-5LAIGWLU.mjs.map +7 -0
- 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-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-G7SDBRKH.mjs +1 -0
- package/dist/lib/browser/chunk-JXCBZSBJ.mjs +372 -0
- package/dist/lib/browser/chunk-JXCBZSBJ.mjs.map +7 -0
- package/dist/lib/browser/chunk-MX5DKEJH.mjs +584 -0
- package/dist/lib/browser/chunk-MX5DKEJH.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-FU4GAFUQ.mjs → chunk-Z55LVAGN.mjs} +80 -15
- package/dist/lib/browser/chunk-Z55LVAGN.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 +11 -27
- package/dist/lib/browser/cli/index.mjs.map +2 -2
- 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 +6 -4
- package/dist/lib/browser/core/plugin.mjs +10 -2
- package/dist/lib/browser/core/url-loader.mjs +13 -5
- package/dist/lib/browser/index.mjs +22 -18
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/{invoker-capability-H5PPENOC.mjs → invoker-capability-LNX4CGIV.mjs} +12 -11
- package/dist/lib/browser/invoker-capability-LNX4CGIV.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +144 -27
- 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-EW5GJCI6.mjs} +10 -10
- package/dist/lib/node-esm/{capability-WFEG6CIZ.mjs → capability-YKBMMD53.mjs} +11 -11
- package/dist/lib/node-esm/capability-YKBMMD53.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-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-D5PO2WXX.mjs +373 -0
- package/dist/lib/node-esm/chunk-D5PO2WXX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-ULUEXB7Q.mjs → chunk-HTBJU5FX.mjs} +80 -15
- package/dist/lib/node-esm/chunk-HTBJU5FX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-KM2F6GH6.mjs +468 -0
- package/dist/lib/node-esm/chunk-KM2F6GH6.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-EL3R25OQ.mjs → chunk-OZ7DZA5Z.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-BCEOLX47.mjs → chunk-Q7XBFII4.mjs} +170 -226
- package/dist/lib/node-esm/chunk-Q7XBFII4.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-G3RTFSNG.mjs → chunk-WFSRZKBP.mjs} +2 -2
- package/dist/lib/node-esm/chunk-WFSRZKBP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WKTLE7MG.mjs +585 -0
- package/dist/lib/node-esm/chunk-WKTLE7MG.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 +11 -27
- package/dist/lib/node-esm/cli/index.mjs.map +2 -2
- 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 +6 -4
- package/dist/lib/node-esm/core/plugin.mjs +10 -2
- package/dist/lib/node-esm/core/url-loader.mjs +13 -5
- package/dist/lib/node-esm/index.mjs +22 -18
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/{invoker-capability-S3ZA527J.mjs → invoker-capability-O4T5PHLA.mjs} +12 -11
- package/dist/lib/node-esm/invoker-capability-O4T5PHLA.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +144 -27
- 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 +480 -32
- package/dist/plugin/node-esm/index.mjs.map +4 -4
- package/dist/plugin/node-esm/meta.json +1 -1
- 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/index.d.ts +2 -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 +51 -2
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manifest.d.ts +76 -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 +107 -6
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/url-loader.d.ts +90 -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/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/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/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 +29 -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 +52 -0
- package/dist/types/src/vite-plugin/boot-loader/index.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 +37 -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 +33 -59
- package/src/common/capabilities.ts +2 -1
- package/src/common/operations.ts +1 -1
- package/src/core/capability.ts +1 -1
- package/src/core/index.ts +2 -0
- package/src/core/plugin-asset-cache.ts +60 -0
- package/src/core/plugin-manager.test.ts +246 -5
- package/src/core/plugin-manager.ts +167 -25
- package/src/core/plugin-manifest.test.ts +48 -0
- package/src/core/plugin-manifest.ts +102 -0
- package/src/core/plugin.ts +135 -10
- package/src/core/url-loader.test.ts +104 -5
- package/src/core/url-loader.ts +226 -37
- 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/testing.ts +2 -1
- package/src/plugin-runtime/RuntimePlugin.ts +2 -2
- 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/withPluginManager.stories.tsx +1 -1
- package/src/ui/components/App/App.stories.tsx +1 -1
- 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 +83 -46
- 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.tsx +165 -41
- package/src/ui/hooks/useLoading.tsx +14 -6
- package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +263 -0
- package/src/vite-plugin/boot-loader/boot-loader.css +294 -0
- package/src/vite-plugin/boot-loader/boot-loader.js +274 -0
- package/src/vite-plugin/boot-loader/index.ts +112 -0
- package/src/vite-plugin/composer/index.ts +277 -0
- package/src/vite-plugin/import-map/index.ts +524 -0
- package/src/vite-plugin/index.ts +6 -2
- package/src/vite-plugin/manifest.test.ts +24 -0
- package/src/vite-plugin/manifest.ts +50 -0
- package/src/vite-plugin/packages.ts +169 -10
- package/tsconfig.json +9 -0
- 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.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-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.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-Q5XRXRD2.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-I34GF4NG.mjs.map → chunk-45CHLTBV.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-RFSO3JRG.mjs.map → chunk-G7SDBRKH.mjs.map} +0 -0
- /package/dist/lib/node-esm/{capability-AWBEMRYR.mjs.map → capability-EW5GJCI6.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs.map → chunk-6XW6LET6.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-EL3R25OQ.mjs.map → chunk-OZ7DZA5Z.mjs.map} +0 -0
|
@@ -16,6 +16,7 @@ import * as Ref from 'effect/Ref';
|
|
|
16
16
|
|
|
17
17
|
import { runAndForwardErrors } from '@dxos/effect';
|
|
18
18
|
import { Performance } from '@dxos/effect';
|
|
19
|
+
import { BaseError } from '@dxos/errors';
|
|
19
20
|
import { log } from '@dxos/log';
|
|
20
21
|
|
|
21
22
|
import * as ActivationEvent from './activation-event';
|
|
@@ -23,6 +24,18 @@ import * as Capability from './capability';
|
|
|
23
24
|
import * as CapabilityManager from './capability-manager';
|
|
24
25
|
import * as Plugin from './plugin';
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Tagged error for failures during the constructor-launched core/enabled
|
|
29
|
+
* `enable()` chain. Surfaces via {@link PluginManager.activate}'s wait on
|
|
30
|
+
* `_initialization` so a caller blocked on initialization gets a typed
|
|
31
|
+
* failure (with the original error preserved as `cause`) instead of an
|
|
32
|
+
* untyped `Error`.
|
|
33
|
+
*/
|
|
34
|
+
export class PluginInitializationError extends BaseError.extend(
|
|
35
|
+
'PluginInitializationError',
|
|
36
|
+
'Plugin manager initialization failed',
|
|
37
|
+
) {}
|
|
38
|
+
|
|
26
39
|
/**
|
|
27
40
|
* Identifier denoting a Manager.
|
|
28
41
|
*/
|
|
@@ -35,6 +48,12 @@ export type ManagerOptions = {
|
|
|
35
48
|
core?: string[];
|
|
36
49
|
enabled?: string[];
|
|
37
50
|
registry?: Registry.Registry;
|
|
51
|
+
/**
|
|
52
|
+
* Hook called when a plugin is removed via {@link PluginManager.remove}. Used by the
|
|
53
|
+
* host app to clean up persisted state (e.g. evict offline-cached plugin assets).
|
|
54
|
+
* Failures are logged and swallowed; removal still succeeds even if the hook fails.
|
|
55
|
+
*/
|
|
56
|
+
onRemove?: (id: string) => Effect.Effect<void, unknown>;
|
|
38
57
|
};
|
|
39
58
|
|
|
40
59
|
export type ActivationMessage = {
|
|
@@ -70,9 +89,14 @@ export interface PluginManager {
|
|
|
70
89
|
getEventsFired(): readonly string[];
|
|
71
90
|
getPendingReset(): readonly string[];
|
|
72
91
|
|
|
73
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Loads a plugin via the plugin loader and registers it without enabling it.
|
|
94
|
+
* Returns the loaded plugin so callers can enable it by its canonical id
|
|
95
|
+
* (which may differ from the locator used to load it, e.g. URL loaders).
|
|
96
|
+
*/
|
|
97
|
+
add(id: string): Effect.Effect<Plugin.Plugin, Error>;
|
|
74
98
|
enable(id: string): Effect.Effect<boolean, Error>;
|
|
75
|
-
remove(id: string): boolean
|
|
99
|
+
remove(id: string): Effect.Effect<boolean, Error>;
|
|
76
100
|
disable(id: string): Effect.Effect<boolean, Error>;
|
|
77
101
|
// TODO(wittjosiah): Improve error typing.
|
|
78
102
|
activate(
|
|
@@ -114,14 +138,29 @@ class ManagerImpl implements PluginManager {
|
|
|
114
138
|
private readonly _eventsFiredAtom: Atom.Writable<string[]>;
|
|
115
139
|
private readonly _pendingResetAtom: Atom.Writable<string[]>;
|
|
116
140
|
private readonly _pluginLoader: ManagerOptions['pluginLoader'];
|
|
141
|
+
private readonly _onRemove: ManagerOptions['onRemove'];
|
|
117
142
|
private readonly _capabilities = new Map<string, Capability.Any[]>();
|
|
118
143
|
private readonly _moduleMemoMap = new Map<Plugin.PluginModule['id'], Deferred.Deferred<Capability.Any[], Error>>();
|
|
119
144
|
private readonly _moduleSemaphores = new Map<Plugin.PluginModule['id'], Effect.Semaphore>();
|
|
145
|
+
// Coalesces concurrent `_resolveLazyPlugin` calls per plugin id. Without
|
|
146
|
+
// this, two callers entering `enable(id)` before the swap completes would
|
|
147
|
+
// each invoke `mod.default(options)` and produce distinct module objects,
|
|
148
|
+
// defeating `_addModule`'s reference-equality dedupe and racing the
|
|
149
|
+
// `_pluginsAtom` swap.
|
|
150
|
+
private readonly _resolvingPlugins = new Map<string, Deferred.Deferred<Plugin.Plugin, Plugin.LazyPluginError>>();
|
|
120
151
|
private readonly _activatingEvents = Effect.runSync(Ref.make<string[]>([]));
|
|
121
152
|
private readonly _activatingModules = Effect.runSync(Ref.make<string[]>([]));
|
|
122
153
|
private readonly _inFlightFibers = Effect.runSync(Ref.make<Array<Fiber.Fiber<unknown, unknown>>>([]));
|
|
123
154
|
private readonly _shutdownSemaphore = Effect.runSync(Effect.makeSemaphore(1));
|
|
124
155
|
private readonly _shuttingDown = Effect.runSync(Ref.make(false));
|
|
156
|
+
// Tracks the constructor-launched core/enabled `enable()` calls so that
|
|
157
|
+
// `activate` can wait for module registration before dispatching events.
|
|
158
|
+
// Lazy plugins make `enable` asynchronous (a dynamic `import()` happens
|
|
159
|
+
// inside it), so without this synchronization an `activate` triggered
|
|
160
|
+
// immediately after `make` could fire on an empty module set. Failures
|
|
161
|
+
// are wrapped in `PluginInitializationError` so awaiters get a tagged
|
|
162
|
+
// error rather than the wide `Error` produced by the underlying chain.
|
|
163
|
+
private readonly _initialization = Effect.runSync(Deferred.make<void, PluginInitializationError>());
|
|
125
164
|
|
|
126
165
|
constructor({
|
|
127
166
|
pluginLoader,
|
|
@@ -129,6 +168,7 @@ class ManagerImpl implements PluginManager {
|
|
|
129
168
|
core = plugins.map(({ meta }) => meta.id),
|
|
130
169
|
enabled = [],
|
|
131
170
|
registry,
|
|
171
|
+
onRemove,
|
|
132
172
|
}: ManagerOptions) {
|
|
133
173
|
this.registry = registry ?? Registry.make();
|
|
134
174
|
this.capabilities = CapabilityManager.make({
|
|
@@ -136,6 +176,7 @@ class ManagerImpl implements PluginManager {
|
|
|
136
176
|
});
|
|
137
177
|
|
|
138
178
|
this._pluginLoader = pluginLoader;
|
|
179
|
+
this._onRemove = onRemove;
|
|
139
180
|
this._pluginsAtom = Atom.make(plugins).pipe(Atom.keepAlive);
|
|
140
181
|
this._coreAtom = Atom.make(core).pipe(Atom.keepAlive);
|
|
141
182
|
this._enabledAtom = Atom.make(enabled).pipe(Atom.keepAlive);
|
|
@@ -144,7 +185,19 @@ class ManagerImpl implements PluginManager {
|
|
|
144
185
|
this._eventsFiredAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
|
|
145
186
|
this._pendingResetAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
|
|
146
187
|
plugins.forEach((plugin) => this._addPlugin(plugin));
|
|
147
|
-
|
|
188
|
+
// Dedupe before mapping to `enable` — `core` and `enabled` may overlap (an
|
|
189
|
+
// app-supplied plugin can be in both), and concurrent `enable(id)` calls
|
|
190
|
+
// for the same id are not idempotent (each would re-run the lazy resolve
|
|
191
|
+
// and double-register modules). `new Set([...])` preserves first-seen
|
|
192
|
+
// order which matches the natural core-before-enabled precedence.
|
|
193
|
+
const initialIds = [...new Set([...core, ...enabled])];
|
|
194
|
+
void Effect.all(initialIds.map((id) => this.enable(id)))
|
|
195
|
+
.pipe(
|
|
196
|
+
Effect.mapError((cause) => new PluginInitializationError({ cause })),
|
|
197
|
+
Effect.tap(() => Deferred.succeed(this._initialization, undefined)),
|
|
198
|
+
Effect.tapErrorCause((cause) => Deferred.failCause(this._initialization, cause)),
|
|
199
|
+
)
|
|
200
|
+
.pipe(runAndForwardErrors);
|
|
148
201
|
}
|
|
149
202
|
|
|
150
203
|
get plugins(): Atom.Atom<readonly Plugin.Plugin[]> {
|
|
@@ -220,14 +273,15 @@ class ManagerImpl implements PluginManager {
|
|
|
220
273
|
|
|
221
274
|
/**
|
|
222
275
|
* Adds a plugin to the manager via the plugin loader.
|
|
276
|
+
* The plugin is registered but not enabled; call `enable` separately to activate it.
|
|
223
277
|
* @param id The id of the plugin.
|
|
224
278
|
*/
|
|
225
|
-
add(id: string): Effect.Effect<
|
|
279
|
+
add(id: string): Effect.Effect<Plugin.Plugin, Error> {
|
|
226
280
|
return Effect.gen(this, function* () {
|
|
227
281
|
log('add plugin', { id });
|
|
228
282
|
const plugin = yield* this._pluginLoader(id);
|
|
229
283
|
this._addPlugin(plugin);
|
|
230
|
-
return
|
|
284
|
+
return plugin;
|
|
231
285
|
});
|
|
232
286
|
}
|
|
233
287
|
|
|
@@ -238,11 +292,13 @@ class ManagerImpl implements PluginManager {
|
|
|
238
292
|
enable(id: string): Effect.Effect<boolean, Error> {
|
|
239
293
|
return Effect.gen(this, function* () {
|
|
240
294
|
log('enable plugin', { id });
|
|
241
|
-
const
|
|
242
|
-
if (!
|
|
295
|
+
const stub = this._getPlugin(id);
|
|
296
|
+
if (!stub) {
|
|
243
297
|
return false;
|
|
244
298
|
}
|
|
245
299
|
|
|
300
|
+
const plugin = yield* this._resolveLazyPlugin(stub);
|
|
301
|
+
|
|
246
302
|
this._update(this._enabledAtom, (enabled) => (enabled.includes(id) ? enabled : [...enabled, id]));
|
|
247
303
|
|
|
248
304
|
plugin.modules.forEach((module) => {
|
|
@@ -260,19 +316,75 @@ class ManagerImpl implements PluginManager {
|
|
|
260
316
|
});
|
|
261
317
|
}
|
|
262
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Resolves a lazy plugin stub (returned by {@link Plugin.lazy}) to its
|
|
321
|
+
* loaded form and swaps it into `_pluginsAtom`. Returns the input unchanged
|
|
322
|
+
* when the plugin is already resolved, so callers can `yield*` this
|
|
323
|
+
* unconditionally. The lazy stub carries `meta` synchronously but its
|
|
324
|
+
* `modules` list is empty until the loader resolves; the swap ensures
|
|
325
|
+
* subsequent enable/disable operations see the resolved plugin.
|
|
326
|
+
*
|
|
327
|
+
* Concurrent calls for the same id are coalesced via `_resolvingPlugins`:
|
|
328
|
+
* the first caller starts the resolution, every subsequent caller awaits
|
|
329
|
+
* the same `Deferred`. On failure we publish a `lazy:<id>` error message
|
|
330
|
+
* and skip the atom swap so the failure is observable to the activation
|
|
331
|
+
* subscriber and a retry can be attempted.
|
|
332
|
+
*/
|
|
333
|
+
private _resolveLazyPlugin(plugin: Plugin.Plugin): Effect.Effect<Plugin.Plugin, Plugin.LazyPluginError> {
|
|
334
|
+
return Effect.gen(this, function* () {
|
|
335
|
+
if (!Plugin.isLazy(plugin)) {
|
|
336
|
+
return plugin;
|
|
337
|
+
}
|
|
338
|
+
const id = plugin.meta.id;
|
|
339
|
+
|
|
340
|
+
const existing = this._resolvingPlugins.get(id);
|
|
341
|
+
if (existing) {
|
|
342
|
+
return yield* Deferred.await(existing);
|
|
343
|
+
}
|
|
344
|
+
const deferred = yield* Deferred.make<Plugin.Plugin, Plugin.LazyPluginError>();
|
|
345
|
+
this._resolvingPlugins.set(id, deferred);
|
|
346
|
+
|
|
347
|
+
return yield* Effect.gen(this, function* () {
|
|
348
|
+
log('resolving lazy plugin', { id });
|
|
349
|
+
yield* PubSub.publish(this.activation, { event: '', state: 'activating', module: `lazy:${id}` });
|
|
350
|
+
const resolvedPlugin = yield* Plugin.resolveLazy(plugin);
|
|
351
|
+
this._update(this._pluginsAtom, (plugins) => plugins.map((p) => (p.meta.id === id ? resolvedPlugin : p)));
|
|
352
|
+
yield* PubSub.publish(this.activation, { event: '', state: 'activated', module: `lazy:${id}` });
|
|
353
|
+
return resolvedPlugin;
|
|
354
|
+
}).pipe(
|
|
355
|
+
Effect.tapError((error) =>
|
|
356
|
+
PubSub.publish(this.activation, { event: '', state: 'error', module: `lazy:${id}`, error }),
|
|
357
|
+
),
|
|
358
|
+
Effect.tap((value) => Deferred.succeed(deferred, value)),
|
|
359
|
+
Effect.tapErrorCause((cause) => Deferred.failCause(deferred, cause)),
|
|
360
|
+
Effect.ensuring(Effect.sync(() => this._resolvingPlugins.delete(id))),
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
263
365
|
/**
|
|
264
366
|
* Removes a plugin from the manager.
|
|
265
367
|
* @param id The id of the plugin.
|
|
266
368
|
*/
|
|
267
|
-
remove(id: string): boolean {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
369
|
+
remove(id: string): Effect.Effect<boolean, Error> {
|
|
370
|
+
return Effect.gen(this, function* () {
|
|
371
|
+
log('remove plugin', { id });
|
|
372
|
+
const disabled = yield* this.disable(id);
|
|
373
|
+
if (!disabled) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
273
376
|
|
|
274
|
-
|
|
275
|
-
|
|
377
|
+
this._removePlugin(id);
|
|
378
|
+
if (this._onRemove) {
|
|
379
|
+
this._runForkedFiber(
|
|
380
|
+
this._onRemove(id).pipe(
|
|
381
|
+
Effect.tapError((error) => Effect.sync(() => log.warn('plugin remove hook failed', { id, error }))),
|
|
382
|
+
Effect.ignore,
|
|
383
|
+
),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
});
|
|
276
388
|
}
|
|
277
389
|
|
|
278
390
|
/**
|
|
@@ -321,6 +433,12 @@ class ManagerImpl implements PluginManager {
|
|
|
321
433
|
return false;
|
|
322
434
|
}
|
|
323
435
|
|
|
436
|
+
// Wait for the constructor's core/enabled `enable()` chain — including
|
|
437
|
+
// any async dynamic imports for lazy plugins — to finish registering
|
|
438
|
+
// modules. Without this, dispatching to an empty module set is the
|
|
439
|
+
// observable symptom of the race.
|
|
440
|
+
yield* Deferred.await(this._initialization);
|
|
441
|
+
|
|
324
442
|
return yield* Effect.withFiberRuntime<boolean, Error>((fiber) =>
|
|
325
443
|
this._activateEvent(key, params, fiber).pipe(
|
|
326
444
|
together(
|
|
@@ -511,6 +629,18 @@ class ManagerImpl implements PluginManager {
|
|
|
511
629
|
return Ref.update(ref, (fibers) => fibers.filter((trackedFiber) => trackedFiber !== fiber));
|
|
512
630
|
}
|
|
513
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Spawns an effect on the default runtime and registers the resulting fiber in
|
|
634
|
+
* `_inFlightFibers` so {@link shutdown} can interrupt it. Used from sync entry
|
|
635
|
+
* points like {@link remove} where there is no enclosing Effect to fork from;
|
|
636
|
+
* inside an Effect chain prefer the existing track/await/untrack pattern.
|
|
637
|
+
*/
|
|
638
|
+
private _runForkedFiber<E>(effect: Effect.Effect<void, E>): void {
|
|
639
|
+
const fiber = Effect.runFork(effect);
|
|
640
|
+
Effect.runSync(this._trackFiber(this._inFlightFibers, fiber));
|
|
641
|
+
Effect.runFork(Fiber.await(fiber).pipe(Effect.andThen(() => this._untrackFiber(this._inFlightFibers, fiber))));
|
|
642
|
+
}
|
|
643
|
+
|
|
514
644
|
//
|
|
515
645
|
// Registration helpers
|
|
516
646
|
//
|
|
@@ -645,7 +775,7 @@ class ManagerImpl implements PluginManager {
|
|
|
645
775
|
): ActivationEvent.ActivationEvent[] {
|
|
646
776
|
return Function.pipe(
|
|
647
777
|
modules,
|
|
648
|
-
Array.flatMap((module) => module.
|
|
778
|
+
Array.flatMap((module) => module.firesBeforeActivation ?? []),
|
|
649
779
|
HashSet.fromIterable,
|
|
650
780
|
HashSet.toValues,
|
|
651
781
|
Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
|
|
@@ -658,7 +788,7 @@ class ManagerImpl implements PluginManager {
|
|
|
658
788
|
): ActivationEvent.ActivationEvent[] {
|
|
659
789
|
return Function.pipe(
|
|
660
790
|
modules,
|
|
661
|
-
Array.flatMap((module) => module.
|
|
791
|
+
Array.flatMap((module) => module.firesAfterActivation ?? []),
|
|
662
792
|
HashSet.fromIterable,
|
|
663
793
|
HashSet.toValues,
|
|
664
794
|
Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
|
|
@@ -670,7 +800,7 @@ class ManagerImpl implements PluginManager {
|
|
|
670
800
|
events: ActivationEvent.ActivationEvent[],
|
|
671
801
|
phase: 'before' | 'after',
|
|
672
802
|
): Effect.Effect<void, Error> {
|
|
673
|
-
const logLabel = phase === 'before' ? '
|
|
803
|
+
const logLabel = phase === 'before' ? 'firesBeforeActivation' : 'firesAfterActivation';
|
|
674
804
|
const eventKey = phase === 'before' ? 'beforeEvents' : 'afterEvents';
|
|
675
805
|
return Function.pipe(
|
|
676
806
|
events,
|
|
@@ -702,7 +832,7 @@ class ManagerImpl implements PluginManager {
|
|
|
702
832
|
): Effect.Effect<Capability.Any[][], Error> {
|
|
703
833
|
return Function.pipe(
|
|
704
834
|
modules,
|
|
705
|
-
Array.map((mod) => this._loadModule(mod)),
|
|
835
|
+
Array.map((mod) => this._loadModule(mod, key)),
|
|
706
836
|
Effect.allWith({ concurrency: 'unbounded' }),
|
|
707
837
|
Effect.catchAll((error) => {
|
|
708
838
|
return Effect.gen(this, function* () {
|
|
@@ -721,8 +851,12 @@ class ManagerImpl implements PluginManager {
|
|
|
721
851
|
modules,
|
|
722
852
|
Array.zip(capabilities),
|
|
723
853
|
Array.map(([module, capabilitySet]) => this._contributeCapabilities(module, capabilitySet)),
|
|
724
|
-
// TODO(wittjosiah): This currently can't be run in parallel
|
|
725
|
-
//
|
|
854
|
+
// TODO(wittjosiah): This currently can't be run in parallel, and inserting
|
|
855
|
+
// any yield between contributions (`Effect.yieldNow()`, `Effect.sleep(0)`)
|
|
856
|
+
// races the `allOf` activation-event resolver — observed as a System
|
|
857
|
+
// Error dialog on warm reloads. Contributions must stay strictly
|
|
858
|
+
// synchronous within an event; React paint slots have to be found at
|
|
859
|
+
// event boundaries higher up the call chain.
|
|
726
860
|
Effect.all,
|
|
727
861
|
Effect.asVoid,
|
|
728
862
|
);
|
|
@@ -737,7 +871,14 @@ class ManagerImpl implements PluginManager {
|
|
|
737
871
|
return semaphore;
|
|
738
872
|
}
|
|
739
873
|
|
|
740
|
-
|
|
874
|
+
// `parentEvent` is the activation event that first triggered this module
|
|
875
|
+
// load — included in `activating`/`activated` PubSub messages so subscribers
|
|
876
|
+
// (e.g. the boot loader's status listener) can associate a module with its
|
|
877
|
+
// triggering event in the trace. The same module may be referenced by
|
|
878
|
+
// multiple events, but module loads are memoized via `_moduleMemoMap`, so
|
|
879
|
+
// only the first event to need it will appear here; later events await the
|
|
880
|
+
// cached deferred without re-publishing.
|
|
881
|
+
private _loadModule = (module: Plugin.PluginModule, parentEvent: string): Effect.Effect<Capability.Any[], Error> =>
|
|
741
882
|
Effect.gen(this, function* () {
|
|
742
883
|
const semaphore = this._getModuleSemaphore(module.id);
|
|
743
884
|
|
|
@@ -753,9 +894,9 @@ class ManagerImpl implements PluginManager {
|
|
|
753
894
|
this._moduleMemoMap.set(module.id, deferred);
|
|
754
895
|
|
|
755
896
|
const loadEffect = Effect.gen(this, function* () {
|
|
756
|
-
log('loading module', { module: module.id });
|
|
897
|
+
log('loading module', { module: module.id, parentEvent });
|
|
757
898
|
performance.mark(`module:${module.id}:start`);
|
|
758
|
-
yield* PubSub.publish(this.activation, { event:
|
|
899
|
+
yield* PubSub.publish(this.activation, { event: parentEvent, state: 'activating', module: module.id });
|
|
759
900
|
const [duration, capabilities] = yield* module
|
|
760
901
|
.activate()
|
|
761
902
|
.pipe(
|
|
@@ -767,9 +908,10 @@ class ManagerImpl implements PluginManager {
|
|
|
767
908
|
const elapsed = Duration.toMillis(duration);
|
|
768
909
|
performance.mark(`module:${module.id}:end`);
|
|
769
910
|
performance.measure(`module:${module.id}`, `module:${module.id}:start`, `module:${module.id}:end`);
|
|
770
|
-
yield* PubSub.publish(this.activation, { event:
|
|
911
|
+
yield* PubSub.publish(this.activation, { event: parentEvent, state: 'activated', module: module.id });
|
|
771
912
|
log('loaded module', {
|
|
772
913
|
module: module.id,
|
|
914
|
+
parentEvent,
|
|
773
915
|
elapsed,
|
|
774
916
|
failed: false,
|
|
775
917
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import * as PluginManifest from './plugin-manifest';
|
|
8
|
+
|
|
9
|
+
describe('PluginManifest', () => {
|
|
10
|
+
describe('parse', () => {
|
|
11
|
+
test('resolves entry and assets relative to manifest URL', ({ expect }) => {
|
|
12
|
+
const resolved = PluginManifest.parse('https://example.com/plugins/foo/manifest.json', {
|
|
13
|
+
id: 'com.example.foo',
|
|
14
|
+
name: 'Foo',
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
assets: ['index.mjs', 'style.css', 'chunks/lib-abc.js'],
|
|
17
|
+
});
|
|
18
|
+
expect(resolved.id).toBe('com.example.foo');
|
|
19
|
+
expect(resolved.entryUrl).toBe('https://example.com/plugins/foo/index.mjs');
|
|
20
|
+
expect(resolved.assetUrls).toEqual([
|
|
21
|
+
'https://example.com/plugins/foo/index.mjs',
|
|
22
|
+
'https://example.com/plugins/foo/style.css',
|
|
23
|
+
'https://example.com/plugins/foo/chunks/lib-abc.js',
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('throws if the canonical entry filename is not listed in assets', ({ expect }) => {
|
|
28
|
+
expect(() =>
|
|
29
|
+
PluginManifest.parse('https://example.com/manifest.json', {
|
|
30
|
+
id: 'a',
|
|
31
|
+
name: 'A',
|
|
32
|
+
version: '0.0.1',
|
|
33
|
+
assets: ['style.css'],
|
|
34
|
+
}),
|
|
35
|
+
).toThrow();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('throws on missing required fields', ({ expect }) => {
|
|
39
|
+
expect(() =>
|
|
40
|
+
PluginManifest.parse('https://example.com/manifest.json', {
|
|
41
|
+
name: 'A',
|
|
42
|
+
version: '0.0.1',
|
|
43
|
+
assets: ['index.mjs'],
|
|
44
|
+
}),
|
|
45
|
+
).toThrow();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
6
|
+
import * as HttpClient from '@effect/platform/HttpClient';
|
|
7
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
8
|
+
import * as HttpClientResponse from '@effect/platform/HttpClientResponse';
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import * as Schema from 'effect/Schema';
|
|
11
|
+
|
|
12
|
+
import { BaseError } from '@dxos/errors';
|
|
13
|
+
import { PLUGIN_ENTRY_FILENAME } from '@dxos/protocols';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tagged error for manifest fetch / parse failures. Construction sites set
|
|
17
|
+
* `context.manifestUrl` and `context.reason` (one of `'fetch-failed' |
|
|
18
|
+
* 'http-error' | 'parse-failed' | 'invalid'`) so handlers can route on the
|
|
19
|
+
* specific failure mode.
|
|
20
|
+
*/
|
|
21
|
+
export class PluginManifestError extends BaseError.extend('PluginManifestError', 'Plugin manifest is invalid') {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Schema for a third-party plugin manifest.
|
|
25
|
+
*
|
|
26
|
+
* The manifest is published as a sibling of the plugin's entry module
|
|
27
|
+
* ({@link PLUGIN_ENTRY_FILENAME}) and advertises every file the plugin needs at
|
|
28
|
+
* runtime so the host can eagerly cache them for offline use.
|
|
29
|
+
*/
|
|
30
|
+
export const Manifest = Schema.Struct({
|
|
31
|
+
id: Schema.String,
|
|
32
|
+
name: Schema.String,
|
|
33
|
+
version: Schema.String,
|
|
34
|
+
assets: Schema.Array(Schema.String),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type Manifest = Schema.Schema.Type<typeof Manifest>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolved manifest with all asset paths converted to absolute URLs.
|
|
41
|
+
*/
|
|
42
|
+
export type ResolvedManifest = {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
version: string;
|
|
46
|
+
entryUrl: string;
|
|
47
|
+
assetUrls: readonly string[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves a parsed manifest's relative asset paths against the manifest's URL.
|
|
52
|
+
*/
|
|
53
|
+
const resolve = (manifestUrl: string, manifest: Manifest): ResolvedManifest => ({
|
|
54
|
+
id: manifest.id,
|
|
55
|
+
name: manifest.name,
|
|
56
|
+
version: manifest.version,
|
|
57
|
+
entryUrl: new URL(PLUGIN_ENTRY_FILENAME, manifestUrl).toString(),
|
|
58
|
+
assetUrls: manifest.assets.map((asset) => new URL(asset, manifestUrl).toString()),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Synchronous decode + validate + resolve for an in-memory manifest payload. Used
|
|
63
|
+
* by tests and tooling that have the manifest body already loaded; the runtime
|
|
64
|
+
* path goes through {@link fetchManifest}.
|
|
65
|
+
*/
|
|
66
|
+
export const parse = (manifestUrl: string, payload: unknown): ResolvedManifest => {
|
|
67
|
+
const manifest = Schema.decodeUnknownSync(Manifest)(payload);
|
|
68
|
+
if (!manifest.assets.includes(PLUGIN_ENTRY_FILENAME)) {
|
|
69
|
+
throw new Error(`Manifest at ${manifestUrl} does not list ${PLUGIN_ENTRY_FILENAME} in assets.`);
|
|
70
|
+
}
|
|
71
|
+
return resolve(manifestUrl, manifest);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fetches and parses a manifest from the given URL via Effect's HTTP client and
|
|
76
|
+
* Schema-driven JSON decoding. All failure modes surface as a single tagged
|
|
77
|
+
* {@link PluginManifestError}; callers route on `error.context.reason`.
|
|
78
|
+
*/
|
|
79
|
+
export const fetchManifest = (manifestUrl: string): Effect.Effect<ResolvedManifest, PluginManifestError> =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const response = yield* HttpClientRequest.get(manifestUrl).pipe(
|
|
82
|
+
HttpClient.execute,
|
|
83
|
+
Effect.mapError((cause) => new PluginManifestError({ context: { manifestUrl, reason: 'fetch-failed' }, cause })),
|
|
84
|
+
);
|
|
85
|
+
if (response.status >= 400) {
|
|
86
|
+
return yield* Effect.fail(
|
|
87
|
+
new PluginManifestError({ context: { manifestUrl, reason: 'http-error', status: response.status } }),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const manifest = yield* HttpClientResponse.schemaBodyJson(Manifest)(response).pipe(
|
|
91
|
+
Effect.mapError((cause) => new PluginManifestError({ context: { manifestUrl, reason: 'parse-failed' }, cause })),
|
|
92
|
+
);
|
|
93
|
+
if (!manifest.assets.includes(PLUGIN_ENTRY_FILENAME)) {
|
|
94
|
+
return yield* Effect.fail(
|
|
95
|
+
new PluginManifestError({
|
|
96
|
+
context: { manifestUrl, reason: 'invalid' },
|
|
97
|
+
cause: `assets does not include ${PLUGIN_ENTRY_FILENAME}`,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return resolve(manifestUrl, manifest);
|
|
102
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer));
|