@dxos/app-framework 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29
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/.storybook/main.mts +9 -0
- package/.storybook/preview.mts +8 -0
- package/dist/lib/browser/capability-5RRH3WIB.mjs +35 -0
- package/dist/lib/browser/capability-5RRH3WIB.mjs.map +7 -0
- package/dist/lib/browser/capability-LUKGKUQH.mjs +38 -0
- package/dist/lib/browser/capability-LUKGKUQH.mjs.map +7 -0
- package/dist/lib/browser/chunk-23D4SJUE.mjs +42 -0
- package/dist/lib/browser/chunk-23D4SJUE.mjs.map +7 -0
- package/dist/lib/browser/chunk-3JWJXGLK.mjs +79 -0
- package/dist/lib/browser/chunk-3JWJXGLK.mjs.map +7 -0
- package/dist/lib/browser/chunk-45CHLTBV.mjs +34 -0
- package/dist/lib/browser/chunk-45CHLTBV.mjs.map +7 -0
- package/dist/lib/browser/chunk-66IXTIVK.mjs +48 -0
- package/dist/lib/browser/chunk-66IXTIVK.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-CZ4BIAHH.mjs +422 -0
- package/dist/lib/browser/chunk-CZ4BIAHH.mjs.map +7 -0
- package/dist/lib/browser/chunk-FJ4765WW.mjs +8 -0
- package/dist/lib/browser/chunk-FJ4765WW.mjs.map +7 -0
- package/dist/lib/browser/chunk-FO3IYSLV.mjs +68 -0
- package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-JD3Z5NEF.mjs +469 -0
- package/dist/lib/browser/chunk-JD3Z5NEF.mjs.map +7 -0
- package/dist/lib/browser/chunk-NBXPP7JR.mjs +1174 -0
- package/dist/lib/browser/chunk-NBXPP7JR.mjs.map +7 -0
- package/dist/lib/browser/chunk-WBHCSOBW.mjs +80 -0
- package/dist/lib/browser/chunk-WBHCSOBW.mjs.map +7 -0
- package/dist/lib/browser/chunk-Z55LVAGN.mjs +213 -0
- package/dist/lib/browser/chunk-Z55LVAGN.mjs.map +7 -0
- package/dist/lib/browser/chunk-ZGJAZSNE.mjs +142 -0
- package/dist/lib/browser/chunk-ZGJAZSNE.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +76 -0
- package/dist/lib/browser/cli/index.mjs.map +7 -0
- package/dist/lib/browser/common/activation-events.mjs +24 -0
- package/dist/lib/browser/common/capabilities.mjs +46 -0
- package/dist/lib/browser/core/activation-event.mjs +20 -0
- package/dist/lib/browser/core/capability.mjs +30 -0
- package/dist/lib/browser/core/capability.mjs.map +7 -0
- package/dist/lib/browser/core/plugin-manager.mjs +19 -0
- package/dist/lib/browser/core/plugin-manager.mjs.map +7 -0
- package/dist/lib/browser/core/plugin.mjs +37 -0
- package/dist/lib/browser/core/plugin.mjs.map +7 -0
- package/dist/lib/browser/core/url-loader.mjs +24 -0
- package/dist/lib/browser/core/url-loader.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +108 -162
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/invoker-capability-K4GHUFXF.mjs +44 -0
- package/dist/lib/browser/invoker-capability-K4GHUFXF.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +235 -48
- 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 +48 -0
- package/dist/lib/browser/ui/index.mjs.map +7 -0
- package/dist/lib/node-esm/capability-FCGZVIEG.mjs +39 -0
- package/dist/lib/node-esm/capability-FCGZVIEG.mjs.map +7 -0
- package/dist/lib/node-esm/capability-JOIQ2MQE.mjs +36 -0
- package/dist/lib/node-esm/capability-JOIQ2MQE.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-37Z53PXZ.mjs +10 -0
- package/dist/lib/node-esm/chunk-37Z53PXZ.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-6XW6LET6.mjs +35 -0
- package/dist/lib/node-esm/chunk-6XW6LET6.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-D347W3KO.mjs +143 -0
- package/dist/lib/node-esm/chunk-D347W3KO.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-DRZKO5UZ.mjs +470 -0
- package/dist/lib/node-esm/chunk-DRZKO5UZ.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HTBJU5FX.mjs +214 -0
- package/dist/lib/node-esm/chunk-HTBJU5FX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-M3HKPRPO.mjs +423 -0
- package/dist/lib/node-esm/chunk-M3HKPRPO.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MUVUQC3G.mjs +1175 -0
- package/dist/lib/node-esm/chunk-MUVUQC3G.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-SBS2YMPT.mjs +43 -0
- package/dist/lib/node-esm/chunk-SBS2YMPT.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-SDJ4B2LU.mjs +80 -0
- package/dist/lib/node-esm/chunk-SDJ4B2LU.mjs.map +7 -0
- 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-WFSRZKBP.mjs +81 -0
- 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-XOCUANHO.mjs +49 -0
- package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +7 -0
- package/dist/lib/node-esm/cli/index.mjs +77 -0
- package/dist/lib/node-esm/cli/index.mjs.map +7 -0
- package/dist/lib/node-esm/common/activation-events.mjs +25 -0
- package/dist/lib/node-esm/common/activation-events.mjs.map +7 -0
- package/dist/lib/node-esm/common/capabilities.mjs +47 -0
- package/dist/lib/node-esm/common/capabilities.mjs.map +7 -0
- package/dist/lib/node-esm/core/activation-event.mjs +21 -0
- package/dist/lib/node-esm/core/activation-event.mjs.map +7 -0
- package/dist/lib/node-esm/core/capability.mjs +31 -0
- package/dist/lib/node-esm/core/capability.mjs.map +7 -0
- package/dist/lib/node-esm/core/plugin-manager.mjs +20 -0
- package/dist/lib/node-esm/core/plugin-manager.mjs.map +7 -0
- package/dist/lib/node-esm/core/plugin.mjs +38 -0
- package/dist/lib/node-esm/core/plugin.mjs.map +7 -0
- package/dist/lib/node-esm/core/url-loader.mjs +25 -0
- package/dist/lib/node-esm/core/url-loader.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +108 -162
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/invoker-capability-XEPW5LMJ.mjs +45 -0
- package/dist/lib/node-esm/invoker-capability-XEPW5LMJ.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +235 -48
- 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 +49 -0
- package/dist/lib/node-esm/ui/index.mjs.map +7 -0
- package/dist/plugin/node-esm/index.mjs +893 -0
- package/dist/plugin/node-esm/index.mjs.map +7 -0
- package/dist/plugin/node-esm/meta.json +1 -0
- package/dist/types/src/cli/cli.d.ts +39 -0
- package/dist/types/src/cli/cli.d.ts.map +1 -0
- package/dist/types/src/cli/index.d.ts +2 -0
- package/dist/types/src/cli/index.d.ts.map +1 -0
- package/dist/types/src/common/activation-events.d.ts +27 -0
- package/dist/types/src/common/activation-events.d.ts.map +1 -0
- 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 +110 -190
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/common/index.d.ts +4 -8
- package/dist/types/src/common/index.d.ts.map +1 -1
- package/dist/types/src/common/operations.d.ts +19 -0
- package/dist/types/src/common/operations.d.ts.map +1 -0
- package/dist/types/src/common/translations.d.ts +8 -8
- package/dist/types/src/common/translations.d.ts.map +1 -1
- package/dist/types/src/context.d.ts +5 -0
- package/dist/types/src/context.d.ts.map +1 -0
- package/dist/types/src/core/{events.d.ts → activation-event.d.ts} +11 -11
- package/dist/types/src/core/activation-event.d.ts.map +1 -0
- package/dist/types/src/core/capability-manager.d.ts +48 -0
- package/dist/types/src/core/capability-manager.d.ts.map +1 -0
- package/dist/types/src/core/capability-manager.test.d.ts +2 -0
- package/dist/types/src/core/capability-manager.test.d.ts.map +1 -0
- package/dist/types/src/core/capability.d.ts +156 -0
- package/dist/types/src/core/capability.d.ts.map +1 -0
- 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 +11 -4
- 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 +239 -0
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager.test.d.ts +2 -0
- package/dist/types/src/core/plugin-manager.test.d.ts.map +1 -0
- 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 +208 -37
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/registry.d.ts +101 -0
- package/dist/types/src/core/registry.d.ts.map +1 -0
- package/dist/types/src/core/url-loader.d.ts +127 -0
- package/dist/types/src/core/url-loader.d.ts.map +1 -0
- package/dist/types/src/core/url-loader.test.d.ts +2 -0
- package/dist/types/src/core/url-loader.test.d.ts.map +1 -0
- package/dist/types/src/helpers.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -4
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/OperationPlugin.d.ts +3 -0
- package/dist/types/src/plugin-operation/OperationPlugin.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/capability.d.ts +7 -0
- package/dist/types/src/plugin-operation/history/capability.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/errors.d.ts +32 -0
- package/dist/types/src/plugin-operation/history/errors.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts +18 -0
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/history-tracker.test.d.ts +2 -0
- package/dist/types/src/plugin-operation/history/history-tracker.test.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/index.d.ts +6 -0
- package/dist/types/src/plugin-operation/history/index.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/types.d.ts +13 -0
- package/dist/types/src/plugin-operation/history/types.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts +101 -0
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts +23 -0
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/history/undo-registry.test.d.ts +2 -0
- package/dist/types/src/plugin-operation/history/undo-registry.test.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/index.d.ts +3 -0
- package/dist/types/src/plugin-operation/index.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/invoker-capability.d.ts +6 -0
- package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/meta.d.ts +3 -0
- package/dist/types/src/plugin-operation/meta.d.ts.map +1 -0
- package/dist/types/src/plugin-operation/testing.d.ts +59 -0
- package/dist/types/src/plugin-operation/testing.d.ts.map +1 -0
- package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts +3 -0
- package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts.map +1 -0
- package/dist/types/src/plugin-runtime/capability.d.ts +6 -0
- package/dist/types/src/plugin-runtime/capability.d.ts.map +1 -0
- package/dist/types/src/plugin-runtime/index.d.ts +2 -0
- package/dist/types/src/plugin-runtime/index.d.ts.map +1 -0
- package/dist/types/src/plugin-runtime/meta.d.ts +3 -0
- package/dist/types/src/plugin-runtime/meta.d.ts.map +1 -0
- 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 +2 -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 +8 -0
- package/dist/types/src/testing/service.d.ts.map +1 -0
- package/dist/types/src/testing/withPluginManager.d.ts +8 -9
- 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 +9 -0
- package/dist/types/src/ui/components/App/App.d.ts.map +1 -0
- package/dist/types/src/ui/components/App/App.stories.d.ts +19 -0
- package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/App/index.d.ts +2 -0
- package/dist/types/src/ui/components/App/index.d.ts.map +1 -0
- 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/{playground/playground.stories.d.ts → ui/components/PluginManager/PluginManagerContext.stories.d.ts} +5 -4
- package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts +10 -0
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts.map +1 -0
- package/dist/types/src/ui/components/PluginManager/index.d.ts +2 -0
- package/dist/types/src/ui/components/PluginManager/index.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +24 -0
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -0
- package/dist/types/src/{components/App.stories.d.ts → ui/components/Surface/SurfaceComponent.stories.d.ts} +1 -2
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts +11 -0
- package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts +48 -0
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/context.d.ts +5 -0
- package/dist/types/src/ui/components/Surface/context.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/index.d.ts +36 -0
- package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/types.d.ts +197 -0
- package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -0
- 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 +5 -0
- package/dist/types/src/ui/components/index.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/index.d.ts +6 -0
- package/dist/types/src/ui/hooks/index.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/useApp.d.ts +89 -0
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/useApp.test.d.ts +2 -0
- package/dist/types/src/ui/hooks/useApp.test.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/useCapabilities.d.ts +31 -0
- package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -0
- package/dist/types/src/{components → ui/hooks}/useLoading.d.ts +1 -2
- package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/useSettingsState.d.ts +10 -0
- package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/useSurface.d.ts +3 -0
- package/dist/types/src/ui/hooks/useSurface.d.ts.map +1 -0
- package/dist/types/src/ui/index.d.ts +3 -0
- package/dist/types/src/ui/index.d.ts.map +1 -0
- 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 +5 -0
- package/dist/types/src/vite-plugin/index.d.ts.map +1 -0
- 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 +13 -0
- package/dist/types/src/vite-plugin/packages.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/moon.yml +26 -3
- package/package.json +114 -54
- package/src/cli/cli.ts +107 -0
- package/src/{playground/logger → cli}/index.ts +1 -1
- package/src/common/activation-events.ts +44 -0
- package/src/{components/index.ts → common/annotations.ts} +0 -2
- package/src/common/capabilities.ts +169 -195
- package/src/common/index.ts +4 -8
- package/src/common/operations.ts +35 -0
- package/src/common/translations.ts +18 -10
- package/src/context.ts +9 -0
- package/src/core/{events.ts → activation-event.ts} +10 -7
- package/src/core/capability-manager.test.ts +151 -0
- package/src/core/capability-manager.ts +192 -0
- package/src/core/capability.ts +247 -0
- package/src/core/edge-registry-plugin-provider.ts +92 -0
- package/src/core/index.ts +11 -4
- package/src/core/plugin-asset-cache.ts +60 -0
- package/src/core/plugin-manager.test.ts +1671 -0
- package/src/core/plugin-manager.ts +1335 -0
- package/src/core/plugin-manifest.test.ts +75 -0
- package/src/core/plugin-manifest.ts +134 -0
- package/src/core/plugin.ts +369 -43
- package/src/core/registry.ts +157 -0
- package/src/core/url-loader.test.ts +221 -0
- package/src/core/url-loader.ts +388 -0
- package/src/index.ts +3 -4
- package/src/plugin-operation/OperationPlugin.ts +24 -0
- package/src/plugin-operation/history/capability.ts +36 -0
- package/src/plugin-operation/history/errors.ts +7 -0
- package/src/plugin-operation/history/history-tracker.test.ts +374 -0
- package/src/plugin-operation/history/history-tracker.ts +128 -0
- package/src/plugin-operation/history/index.ts +9 -0
- package/src/plugin-operation/history/types.ts +17 -0
- package/src/plugin-operation/history/undo-mapping.ts +135 -0
- package/src/plugin-operation/history/undo-registry.test.ts +72 -0
- package/src/plugin-operation/history/undo-registry.ts +54 -0
- package/src/plugin-operation/index.ts +6 -0
- package/src/plugin-operation/invoker-capability.ts +55 -0
- package/src/plugin-operation/meta.ts +12 -0
- package/src/plugin-operation/testing.ts +155 -0
- package/src/plugin-runtime/RuntimePlugin.ts +19 -0
- package/src/plugin-runtime/capability.ts +53 -0
- package/src/plugin-runtime/index.ts +5 -0
- package/src/plugin-runtime/meta.ts +12 -0
- package/src/testing/harness.ts +229 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/react.test.tsx +48 -0
- package/src/testing/react.tsx +113 -0
- package/src/testing/service.ts +52 -0
- package/src/testing/withPluginManager.stories.tsx +9 -10
- package/src/testing/withPluginManager.tsx +74 -50
- package/src/ui/components/App/App.stories.tsx +88 -0
- package/src/ui/components/App/App.tsx +81 -0
- package/src/{playground/layout → ui/components/App}/index.ts +1 -1
- package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
- package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
- package/src/{playground/debug → ui/components/Placeholder}/index.ts +1 -1
- package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +185 -0
- package/src/{react → ui/components/PluginManager}/PluginManagerProvider.ts +3 -3
- package/src/ui/components/PluginManager/index.ts +5 -0
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +144 -0
- package/src/ui/components/Surface/SurfaceComponent.tsx +303 -0
- package/src/ui/components/Surface/SurfaceInfo.tsx +106 -0
- package/src/ui/components/Surface/SurfaceProfilerContext.tsx +207 -0
- package/src/ui/components/Surface/context.ts +12 -0
- package/src/ui/components/Surface/index.ts +54 -0
- package/src/ui/components/Surface/types.test.ts +126 -0
- package/src/ui/components/Surface/types.ts +269 -0
- package/src/ui/components/index.ts +8 -0
- package/src/ui/hooks/index.ts +9 -0
- package/src/ui/hooks/useApp.test.tsx +159 -0
- package/src/ui/hooks/useApp.tsx +424 -0
- package/src/ui/hooks/useCapabilities.ts +67 -0
- package/src/{components → ui/hooks}/useLoading.tsx +16 -10
- package/src/ui/hooks/useSettingsState.ts +26 -0
- package/src/ui/hooks/useSurface.ts +13 -0
- package/src/ui/index.ts +6 -0
- package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +270 -0
- package/src/vite-plugin/boot-loader/boot-loader.css +320 -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 +10 -0
- package/src/vite-plugin/manifest.test.ts +46 -0
- package/src/vite-plugin/manifest.ts +57 -0
- package/src/vite-plugin/packages.ts +187 -0
- package/tsconfig.json +24 -15
- package/tsconfig.node.json +2 -4
- package/typedoc.json +2 -4
- package/vitest.config.ts +8 -6
- package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
- package/.swc/plugins/linux_x86_64_19.0.0/fce1bdb8e20a094e4af08bad09cc81497ed0e2e7c51223b07d371063cca18429.wasmer-v7 +0 -0
- package/dist/lib/browser/app-graph-builder-AFFC6VB2.mjs +0 -137
- package/dist/lib/browser/app-graph-builder-AFFC6VB2.mjs.map +0 -7
- package/dist/lib/browser/chunk-ORWHM7CO.mjs +0 -32
- package/dist/lib/browser/chunk-ORWHM7CO.mjs.map +0 -7
- package/dist/lib/browser/chunk-OZY7HV2A.mjs +0 -1643
- package/dist/lib/browser/chunk-OZY7HV2A.mjs.map +0 -7
- package/dist/lib/browser/chunk-T6M7JB7M.mjs +0 -471
- package/dist/lib/browser/chunk-T6M7JB7M.mjs.map +0 -7
- package/dist/lib/browser/intent-dispatcher-QG7UPGQX.mjs +0 -11
- package/dist/lib/browser/intent-resolver-4S4PSTM5.mjs +0 -39
- package/dist/lib/browser/intent-resolver-4S4PSTM5.mjs.map +0 -7
- package/dist/lib/browser/store-6E33KLGK.mjs +0 -30
- package/dist/lib/browser/store-6E33KLGK.mjs.map +0 -7
- package/dist/lib/browser/worker.mjs +0 -85
- package/dist/lib/node-esm/app-graph-builder-S4OAULX5.mjs +0 -138
- package/dist/lib/node-esm/app-graph-builder-S4OAULX5.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-F63ZRXMK.mjs +0 -1645
- package/dist/lib/node-esm/chunk-F63ZRXMK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HJFU7QOR.mjs +0 -472
- package/dist/lib/node-esm/chunk-HJFU7QOR.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-UMZQERLE.mjs +0 -34
- package/dist/lib/node-esm/chunk-UMZQERLE.mjs.map +0 -7
- package/dist/lib/node-esm/intent-dispatcher-NXBGPJOX.mjs +0 -12
- package/dist/lib/node-esm/intent-resolver-2ZKXI5ET.mjs +0 -40
- package/dist/lib/node-esm/intent-resolver-2ZKXI5ET.mjs.map +0 -7
- package/dist/lib/node-esm/store-QQUTQHHT.mjs +0 -31
- package/dist/lib/node-esm/store-QQUTQHHT.mjs.map +0 -7
- package/dist/lib/node-esm/worker.mjs +0 -86
- package/dist/types/src/common/collaboration.d.ts +0 -20
- package/dist/types/src/common/collaboration.d.ts.map +0 -1
- package/dist/types/src/common/events.d.ts +0 -52
- package/dist/types/src/common/events.d.ts.map +0 -1
- package/dist/types/src/common/file.d.ts +0 -14
- package/dist/types/src/common/file.d.ts.map +0 -1
- package/dist/types/src/common/graph.d.ts +0 -21
- package/dist/types/src/common/graph.d.ts.map +0 -1
- package/dist/types/src/common/layout.d.ts +0 -281
- package/dist/types/src/common/layout.d.ts.map +0 -1
- package/dist/types/src/common/surface.d.ts +0 -65
- package/dist/types/src/common/surface.d.ts.map +0 -1
- package/dist/types/src/components/App.d.ts +0 -10
- package/dist/types/src/components/App.d.ts.map +0 -1
- package/dist/types/src/components/App.stories.d.ts.map +0 -1
- package/dist/types/src/components/DefaultFallback.d.ts +0 -8
- package/dist/types/src/components/DefaultFallback.d.ts.map +0 -1
- package/dist/types/src/components/index.d.ts +0 -2
- package/dist/types/src/components/index.d.ts.map +0 -1
- package/dist/types/src/components/useApp.d.ts +0 -44
- package/dist/types/src/components/useApp.d.ts.map +0 -1
- package/dist/types/src/components/useLoading.d.ts.map +0 -1
- package/dist/types/src/core/capabilities.d.ts +0 -117
- package/dist/types/src/core/capabilities.d.ts.map +0 -1
- package/dist/types/src/core/capabilities.test.d.ts +0 -2
- package/dist/types/src/core/capabilities.test.d.ts.map +0 -1
- package/dist/types/src/core/events.d.ts.map +0 -1
- package/dist/types/src/core/manager.d.ts +0 -126
- package/dist/types/src/core/manager.d.ts.map +0 -1
- package/dist/types/src/core/manager.test.d.ts +0 -2
- package/dist/types/src/core/manager.test.d.ts.map +0 -1
- package/dist/types/src/playground/debug/Debug.d.ts +0 -6
- package/dist/types/src/playground/debug/Debug.d.ts.map +0 -1
- package/dist/types/src/playground/debug/index.d.ts +0 -2
- package/dist/types/src/playground/debug/index.d.ts.map +0 -1
- package/dist/types/src/playground/debug/plugin.d.ts +0 -2
- package/dist/types/src/playground/debug/plugin.d.ts.map +0 -1
- package/dist/types/src/playground/generator/Main.d.ts +0 -6
- package/dist/types/src/playground/generator/Main.d.ts.map +0 -1
- package/dist/types/src/playground/generator/Toolbar.d.ts +0 -6
- package/dist/types/src/playground/generator/Toolbar.d.ts.map +0 -1
- package/dist/types/src/playground/generator/generator.d.ts +0 -7
- package/dist/types/src/playground/generator/generator.d.ts.map +0 -1
- package/dist/types/src/playground/generator/index.d.ts +0 -3
- package/dist/types/src/playground/generator/index.d.ts.map +0 -1
- package/dist/types/src/playground/generator/plugin.d.ts +0 -2
- package/dist/types/src/playground/generator/plugin.d.ts.map +0 -1
- package/dist/types/src/playground/layout/Layout.d.ts +0 -8
- package/dist/types/src/playground/layout/Layout.d.ts.map +0 -1
- package/dist/types/src/playground/layout/index.d.ts +0 -2
- package/dist/types/src/playground/layout/index.d.ts.map +0 -1
- package/dist/types/src/playground/layout/plugin.d.ts +0 -2
- package/dist/types/src/playground/layout/plugin.d.ts.map +0 -1
- package/dist/types/src/playground/logger/Toolbar.d.ts +0 -6
- package/dist/types/src/playground/logger/Toolbar.d.ts.map +0 -1
- package/dist/types/src/playground/logger/index.d.ts +0 -2
- package/dist/types/src/playground/logger/index.d.ts.map +0 -1
- package/dist/types/src/playground/logger/plugin.d.ts +0 -2
- package/dist/types/src/playground/logger/plugin.d.ts.map +0 -1
- package/dist/types/src/playground/logger/schema.d.ts +0 -13
- package/dist/types/src/playground/logger/schema.d.ts.map +0 -1
- package/dist/types/src/playground/playground.stories.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/IntentPlugin.d.ts +0 -2
- package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/actions.d.ts +0 -38
- package/dist/types/src/plugin-intent/actions.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/errors.d.ts +0 -16
- package/dist/types/src/plugin-intent/errors.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/index.d.ts +0 -6
- package/dist/types/src/plugin-intent/index.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +0 -139
- package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/intent-dispatcher.test.d.ts +0 -2
- package/dist/types/src/plugin-intent/intent-dispatcher.test.d.ts.map +0 -1
- package/dist/types/src/plugin-intent/intent.d.ts +0 -63
- package/dist/types/src/plugin-intent/intent.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/SettingsPlugin.d.ts +0 -2
- package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/actions.d.ts +0 -27
- package/dist/types/src/plugin-settings/actions.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/app-graph-builder.d.ts +0 -4
- package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/index.d.ts +0 -3
- package/dist/types/src/plugin-settings/index.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/intent-resolver.d.ts +0 -4
- package/dist/types/src/plugin-settings/intent-resolver.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/store.d.ts +0 -5
- package/dist/types/src/plugin-settings/store.d.ts.map +0 -1
- package/dist/types/src/plugin-settings/translations.d.ts +0 -10
- package/dist/types/src/plugin-settings/translations.d.ts.map +0 -1
- package/dist/types/src/react/ErrorBoundary.d.ts +0 -30
- package/dist/types/src/react/ErrorBoundary.d.ts.map +0 -1
- package/dist/types/src/react/IntentContext.d.ts +0 -8
- package/dist/types/src/react/IntentContext.d.ts.map +0 -1
- package/dist/types/src/react/PluginManagerProvider.d.ts +0 -10
- package/dist/types/src/react/PluginManagerProvider.d.ts.map +0 -1
- package/dist/types/src/react/Surface.d.ts +0 -12
- package/dist/types/src/react/Surface.d.ts.map +0 -1
- package/dist/types/src/react/Surface.stories.d.ts +0 -18
- package/dist/types/src/react/Surface.stories.d.ts.map +0 -1
- package/dist/types/src/react/common.d.ts +0 -13
- package/dist/types/src/react/common.d.ts.map +0 -1
- package/dist/types/src/react/index.d.ts +0 -7
- package/dist/types/src/react/index.d.ts.map +0 -1
- package/dist/types/src/react/useCapabilities.d.ts +0 -13
- package/dist/types/src/react/useCapabilities.d.ts.map +0 -1
- package/dist/types/src/react/useIntentResolver.d.ts +0 -3
- package/dist/types/src/react/useIntentResolver.d.ts.map +0 -1
- package/dist/types/src/worker.d.ts +0 -4
- package/dist/types/src/worker.d.ts.map +0 -1
- package/src/common/collaboration.ts +0 -18
- package/src/common/events.ts +0 -79
- package/src/common/file.ts +0 -22
- package/src/common/graph.ts +0 -30
- package/src/common/layout.ts +0 -278
- package/src/common/surface.ts +0 -86
- package/src/components/App.stories.tsx +0 -35
- package/src/components/App.tsx +0 -59
- package/src/components/DefaultFallback.tsx +0 -26
- package/src/components/useApp.tsx +0 -166
- package/src/core/capabilities.test.ts +0 -136
- package/src/core/capabilities.ts +0 -259
- package/src/core/manager.test.ts +0 -516
- package/src/core/manager.ts +0 -597
- package/src/playground/debug/Debug.tsx +0 -39
- package/src/playground/debug/plugin.ts +0 -17
- package/src/playground/generator/Main.tsx +0 -71
- package/src/playground/generator/Toolbar.tsx +0 -47
- package/src/playground/generator/generator.ts +0 -48
- package/src/playground/generator/index.ts +0 -6
- package/src/playground/generator/plugin.ts +0 -23
- package/src/playground/layout/Layout.tsx +0 -33
- package/src/playground/layout/plugin.ts +0 -17
- package/src/playground/logger/Toolbar.tsx +0 -30
- package/src/playground/logger/plugin.ts +0 -37
- package/src/playground/logger/schema.ts +0 -12
- package/src/playground/playground.stories.tsx +0 -47
- package/src/plugin-intent/IntentPlugin.ts +0 -21
- package/src/plugin-intent/actions.ts +0 -33
- package/src/plugin-intent/errors.ts +0 -39
- package/src/plugin-intent/index.ts +0 -9
- package/src/plugin-intent/intent-dispatcher.test.ts +0 -279
- package/src/plugin-intent/intent-dispatcher.ts +0 -335
- package/src/plugin-intent/intent.ts +0 -154
- package/src/plugin-settings/SettingsPlugin.ts +0 -36
- package/src/plugin-settings/actions.ts +0 -29
- package/src/plugin-settings/app-graph-builder.ts +0 -158
- package/src/plugin-settings/index.ts +0 -6
- package/src/plugin-settings/intent-resolver.ts +0 -35
- package/src/plugin-settings/store.ts +0 -33
- package/src/plugin-settings/translations.ts +0 -19
- package/src/react/ErrorBoundary.tsx +0 -54
- package/src/react/IntentContext.tsx +0 -35
- package/src/react/Surface.stories.tsx +0 -104
- package/src/react/Surface.tsx +0 -78
- package/src/react/common.ts +0 -13
- package/src/react/index.ts +0 -10
- package/src/react/useCapabilities.ts +0 -31
- package/src/react/useIntentResolver.ts +0 -22
- package/src/worker.ts +0 -11
- /package/dist/lib/{node-esm/worker.mjs.map → browser/chunk-J5LGTIGS.mjs.map} +0 -0
- /package/dist/lib/{node-esm/intent-dispatcher-NXBGPJOX.mjs.map → browser/common/activation-events.mjs.map} +0 -0
- /package/dist/lib/browser/{worker.mjs.map → common/capabilities.mjs.map} +0 -0
- /package/dist/lib/browser/{intent-dispatcher-QG7UPGQX.mjs.map → core/activation-event.mjs.map} +0 -0
|
@@ -0,0 +1,1671 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Atom, Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import { afterEach, assert, describe, it } from '@effect/vitest';
|
|
7
|
+
import * as Cause from 'effect/Cause';
|
|
8
|
+
import * as Duration from 'effect/Duration';
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import * as Exit from 'effect/Exit';
|
|
11
|
+
import * as Fiber from 'effect/Fiber';
|
|
12
|
+
import * as Match from 'effect/Match';
|
|
13
|
+
import * as PubSub from 'effect/PubSub';
|
|
14
|
+
import * as Queue from 'effect/Queue';
|
|
15
|
+
import * as TestClock from 'effect/TestClock';
|
|
16
|
+
|
|
17
|
+
import { invariant } from '@dxos/invariant';
|
|
18
|
+
import { type LogConfig, type LogEntry, LogLevel, log } from '@dxos/log';
|
|
19
|
+
|
|
20
|
+
import { ActivationEvents } from '../common';
|
|
21
|
+
import * as ActivationEvent from './activation-event';
|
|
22
|
+
import * as Capability from './capability';
|
|
23
|
+
import type * as CapabilityManager from './capability-manager';
|
|
24
|
+
import * as Plugin from './plugin';
|
|
25
|
+
import * as PluginManager from './plugin-manager';
|
|
26
|
+
|
|
27
|
+
const String = Capability.make<{ string: string }>('org.dxos.test.string');
|
|
28
|
+
const Number = Capability.make<{ number: number }>('org.dxos.test.number');
|
|
29
|
+
const Total = Capability.make<{ total: number }>('org.dxos.test.total');
|
|
30
|
+
|
|
31
|
+
const CountEvent = ActivationEvent.make('org.dxos.test.count');
|
|
32
|
+
const FailEvent = ActivationEvent.make('org.dxos.test.fail');
|
|
33
|
+
|
|
34
|
+
const testMeta = { id: 'org.dxos.plugin.test', name: 'Test' };
|
|
35
|
+
|
|
36
|
+
// TODO(wittjosiah): Factor out?
|
|
37
|
+
const atomCounter = (registry: Registry.Registry, atom: Atom.Atom<any>) => {
|
|
38
|
+
let count = 0;
|
|
39
|
+
let initial = true;
|
|
40
|
+
const dispose = registry.subscribe(
|
|
41
|
+
atom,
|
|
42
|
+
() => {
|
|
43
|
+
if (initial) {
|
|
44
|
+
initial = false;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
count++;
|
|
48
|
+
},
|
|
49
|
+
{ immediate: true },
|
|
50
|
+
);
|
|
51
|
+
return {
|
|
52
|
+
get count() {
|
|
53
|
+
return count;
|
|
54
|
+
},
|
|
55
|
+
[Symbol.dispose]: dispose,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
describe('PluginManager', () => {
|
|
60
|
+
let plugins: Plugin.Plugin[] = [];
|
|
61
|
+
const pluginLoader = Effect.fn(function* (id: string) {
|
|
62
|
+
const plugin = plugins.find((plugin) => plugin.meta.id === id);
|
|
63
|
+
invariant(plugin, `Plugin not found: ${id}`);
|
|
64
|
+
return { plugin };
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
plugins = [];
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it.effect('should be able to add and remove plugins', () =>
|
|
72
|
+
Effect.gen(function* () {
|
|
73
|
+
const Test = Plugin.make(Plugin.define(testMeta));
|
|
74
|
+
const testPlugin = Test();
|
|
75
|
+
plugins = [testPlugin];
|
|
76
|
+
|
|
77
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
78
|
+
const added = yield* manager.add(testMeta.id);
|
|
79
|
+
assert.strictEqual(added, testPlugin);
|
|
80
|
+
assert.deepStrictEqual(manager.getPlugins(), [testPlugin]);
|
|
81
|
+
assert.deepStrictEqual(manager.getEnabled(), []);
|
|
82
|
+
const removed = yield* manager.remove(testMeta.id);
|
|
83
|
+
assert.isTrue(removed);
|
|
84
|
+
assert.deepStrictEqual(manager.getPlugins(), []);
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
it.effect('should add plugin when locator differs from meta.id', () =>
|
|
89
|
+
Effect.gen(function* () {
|
|
90
|
+
const Test = Plugin.make(Plugin.define(testMeta));
|
|
91
|
+
const testPlugin = Test();
|
|
92
|
+
|
|
93
|
+
const urlLocator = 'https://example.com/plugin.mjs';
|
|
94
|
+
const urlLoader = Effect.fn(function* (locator: string) {
|
|
95
|
+
if (locator === urlLocator) {
|
|
96
|
+
return { plugin: testPlugin };
|
|
97
|
+
}
|
|
98
|
+
return yield* Effect.fail(new Error(`Unknown locator: ${locator}`));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const manager = PluginManager.make({ pluginLoader: urlLoader });
|
|
102
|
+
const added = yield* manager.add(urlLocator);
|
|
103
|
+
assert.strictEqual(added, testPlugin);
|
|
104
|
+
assert.deepStrictEqual(manager.getPlugins(), [testPlugin]);
|
|
105
|
+
assert.deepStrictEqual(manager.getEnabled(), []);
|
|
106
|
+
yield* manager.enable(added.meta.id);
|
|
107
|
+
assert.deepStrictEqual(manager.getEnabled(), [testMeta.id]);
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it.effect('dev plugin shadows an existing plugin with the same id', () =>
|
|
112
|
+
Effect.gen(function* () {
|
|
113
|
+
const productionPlugin = Plugin.make(
|
|
114
|
+
Plugin.define(testMeta).pipe(
|
|
115
|
+
Plugin.addModule({
|
|
116
|
+
id: 'Prod',
|
|
117
|
+
activatesOn: ActivationEvents.Startup,
|
|
118
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'prod' })),
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
)();
|
|
122
|
+
const devPlugin = Plugin.make(
|
|
123
|
+
Plugin.define(testMeta).pipe(
|
|
124
|
+
Plugin.addModule({
|
|
125
|
+
id: 'Dev',
|
|
126
|
+
activatesOn: ActivationEvents.Startup,
|
|
127
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'dev' })),
|
|
128
|
+
}),
|
|
129
|
+
),
|
|
130
|
+
)();
|
|
131
|
+
|
|
132
|
+
const loader = Effect.fn(function* (locator: string) {
|
|
133
|
+
if (locator === 'prod') {
|
|
134
|
+
return { plugin: productionPlugin };
|
|
135
|
+
}
|
|
136
|
+
if (locator === 'dev') {
|
|
137
|
+
return { plugin: devPlugin, dev: true };
|
|
138
|
+
}
|
|
139
|
+
return yield* Effect.fail(new Error(`Unknown locator: ${locator}`));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const manager = PluginManager.make({ pluginLoader: loader });
|
|
143
|
+
yield* manager.add('prod');
|
|
144
|
+
yield* manager.enable(testMeta.id);
|
|
145
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
146
|
+
assert.deepStrictEqual(
|
|
147
|
+
manager.capabilities.getAll(String).map((value) => value.string),
|
|
148
|
+
['prod'],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Loading the dev plugin with the same id swaps it into the id slot.
|
|
152
|
+
yield* manager.add('dev');
|
|
153
|
+
yield* manager.enable(testMeta.id);
|
|
154
|
+
assert.strictEqual(
|
|
155
|
+
manager.getPlugins().find((plugin) => plugin.meta.id === testMeta.id),
|
|
156
|
+
devPlugin,
|
|
157
|
+
);
|
|
158
|
+
yield* manager.reset(ActivationEvents.Startup);
|
|
159
|
+
assert.deepStrictEqual(
|
|
160
|
+
manager.capabilities.getAll(String).map((value) => value.string),
|
|
161
|
+
['dev'],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Removing the dev plugin restores the original and re-enables it
|
|
165
|
+
// because it was enabled at shadow time.
|
|
166
|
+
yield* manager.remove(testMeta.id);
|
|
167
|
+
assert.strictEqual(
|
|
168
|
+
manager.getPlugins().find((plugin) => plugin.meta.id === testMeta.id),
|
|
169
|
+
productionPlugin,
|
|
170
|
+
);
|
|
171
|
+
assert.isTrue(manager.getEnabled().includes(testMeta.id));
|
|
172
|
+
yield* manager.reset(ActivationEvents.Startup);
|
|
173
|
+
assert.deepStrictEqual(
|
|
174
|
+
manager.capabilities.getAll(String).map((value) => value.string),
|
|
175
|
+
['prod'],
|
|
176
|
+
);
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
it.effect('dev plugin add does not auto-enable a previously-disabled shadow target', () =>
|
|
181
|
+
Effect.gen(function* () {
|
|
182
|
+
const productionPlugin = Plugin.make(Plugin.define(testMeta))();
|
|
183
|
+
const devPlugin = Plugin.make(Plugin.define(testMeta))();
|
|
184
|
+
const loader = Effect.fn(function* (locator: string) {
|
|
185
|
+
if (locator === 'prod') {
|
|
186
|
+
return { plugin: productionPlugin };
|
|
187
|
+
}
|
|
188
|
+
if (locator === 'dev') {
|
|
189
|
+
return { plugin: devPlugin, dev: true };
|
|
190
|
+
}
|
|
191
|
+
return yield* Effect.fail(new Error(`Unknown locator: ${locator}`));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const manager = PluginManager.make({ pluginLoader: loader });
|
|
195
|
+
yield* manager.add('prod');
|
|
196
|
+
// Production plugin is registered but explicitly NOT enabled.
|
|
197
|
+
assert.deepStrictEqual(manager.getEnabled(), []);
|
|
198
|
+
|
|
199
|
+
yield* manager.add('dev');
|
|
200
|
+
yield* manager.remove(testMeta.id);
|
|
201
|
+
|
|
202
|
+
// Original is restored but stays disabled, matching its pre-shadow state.
|
|
203
|
+
assert.strictEqual(
|
|
204
|
+
manager.getPlugins().find((plugin) => plugin.meta.id === testMeta.id),
|
|
205
|
+
productionPlugin,
|
|
206
|
+
);
|
|
207
|
+
assert.deepStrictEqual(manager.getEnabled(), []);
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
it.effect('should support factory pattern with options', () =>
|
|
212
|
+
Effect.gen(function* () {
|
|
213
|
+
type TestPluginOptions = { count: number };
|
|
214
|
+
const TestPluginFactory = Plugin.define<TestPluginOptions>(testMeta).pipe(
|
|
215
|
+
Plugin.addModule((options: TestPluginOptions) => ({
|
|
216
|
+
id: 'Hello',
|
|
217
|
+
activatesOn: ActivationEvents.Startup,
|
|
218
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: `hello-${options.count}` })),
|
|
219
|
+
})),
|
|
220
|
+
Plugin.addModule({
|
|
221
|
+
id: 'World',
|
|
222
|
+
activatesOn: ActivationEvents.Startup,
|
|
223
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'world' })),
|
|
224
|
+
}),
|
|
225
|
+
Plugin.make,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const plugin = TestPluginFactory({ count: 5 });
|
|
229
|
+
plugins = [plugin];
|
|
230
|
+
|
|
231
|
+
const manager = PluginManager.make({ plugins: [plugin], core: [], pluginLoader });
|
|
232
|
+
yield* manager.enable(testMeta.id);
|
|
233
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
234
|
+
const strings = manager.capabilities.getAll(String);
|
|
235
|
+
assert.strictEqual(strings.length, 2);
|
|
236
|
+
assert.strictEqual(strings[0].string, 'hello-5');
|
|
237
|
+
assert.strictEqual(strings[1].string, 'world');
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
it.effect('should be able to enable and disable plugins', () =>
|
|
242
|
+
Effect.gen(function* () {
|
|
243
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
244
|
+
Plugin.addModule({
|
|
245
|
+
id: 'Hello',
|
|
246
|
+
activatesOn: ActivationEvents.Startup,
|
|
247
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
248
|
+
}),
|
|
249
|
+
Plugin.make,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const testPlugin = Test();
|
|
253
|
+
const manager = PluginManager.make({ plugins: [testPlugin], core: [], pluginLoader });
|
|
254
|
+
yield* manager.enable(testMeta.id);
|
|
255
|
+
assert.deepStrictEqual(manager.getEnabled(), [Test.meta.id]);
|
|
256
|
+
assert.deepStrictEqual(manager.getModules(), [testPlugin.modules[0]]);
|
|
257
|
+
yield* manager.disable(testMeta.id);
|
|
258
|
+
assert.deepStrictEqual(manager.getEnabled(), []);
|
|
259
|
+
assert.deepStrictEqual(manager.getModules(), []);
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
it.effect('should be able to activate plugins', () =>
|
|
264
|
+
Effect.gen(function* () {
|
|
265
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
266
|
+
Plugin.addModule({
|
|
267
|
+
id: 'Hello',
|
|
268
|
+
activatesOn: ActivationEvents.Startup,
|
|
269
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
270
|
+
}),
|
|
271
|
+
Plugin.make,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const testPlugin = Test();
|
|
275
|
+
const manager = PluginManager.make({ plugins: [testPlugin], pluginLoader });
|
|
276
|
+
yield* manager.enable(Test.meta.id);
|
|
277
|
+
assert.deepStrictEqual(manager.getPlugins(), [testPlugin]);
|
|
278
|
+
assert.deepStrictEqual(manager.getEnabled(), [Test.meta.id]);
|
|
279
|
+
assert.deepStrictEqual(manager.getModules(), [testPlugin.modules[0]]);
|
|
280
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
281
|
+
assert.deepStrictEqual(manager.getEventsFired(), []);
|
|
282
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
283
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
284
|
+
assert.deepStrictEqual(manager.getEventsFired(), [ActivationEvents.Startup.id]);
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
it.effect('should handle activate returning void', () =>
|
|
289
|
+
Effect.gen(function* () {
|
|
290
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
291
|
+
Plugin.addModule({
|
|
292
|
+
id: 'NoCapabilities',
|
|
293
|
+
activatesOn: ActivationEvents.Startup,
|
|
294
|
+
activate: Effect.fnUntraced(function* () {}),
|
|
295
|
+
}),
|
|
296
|
+
Plugin.make,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const testPlugin = Test();
|
|
300
|
+
const manager = PluginManager.make({ plugins: [testPlugin], pluginLoader });
|
|
301
|
+
yield* manager.enable(Test.meta.id);
|
|
302
|
+
|
|
303
|
+
const result = yield* manager.activate(ActivationEvents.Startup);
|
|
304
|
+
assert.isTrue(result);
|
|
305
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
306
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 0);
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
it.effect('should propagate errors thrown by module activate callbacks', () =>
|
|
311
|
+
Effect.gen(function* () {
|
|
312
|
+
plugins = [
|
|
313
|
+
Plugin.define(testMeta).pipe(
|
|
314
|
+
Plugin.addModule({
|
|
315
|
+
activatesOn: FailEvent,
|
|
316
|
+
id: 'Fail',
|
|
317
|
+
activate: () => Effect.fail(new Error('test')),
|
|
318
|
+
}),
|
|
319
|
+
Plugin.make,
|
|
320
|
+
)(),
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
324
|
+
yield* manager.add(testMeta.id);
|
|
325
|
+
yield* manager.enable(testMeta.id);
|
|
326
|
+
const error = yield* Effect.flip(manager.activate(FailEvent));
|
|
327
|
+
assert.strictEqual(error.message, 'test');
|
|
328
|
+
}),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
it.effect('should catch and log defects (synchronous throws) in module activation', () =>
|
|
332
|
+
Effect.gen(function* () {
|
|
333
|
+
const DefectEvent = ActivationEvent.make('org.dxos.test.defect');
|
|
334
|
+
const capturedErrors: LogEntry[] = [];
|
|
335
|
+
const removeProcessor = log.addProcessor((_config: LogConfig, entry: LogEntry) => {
|
|
336
|
+
if (entry.level === LogLevel.ERROR) {
|
|
337
|
+
capturedErrors.push(entry);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
plugins = [
|
|
342
|
+
Plugin.define(testMeta).pipe(
|
|
343
|
+
Plugin.addModule({
|
|
344
|
+
activatesOn: DefectEvent,
|
|
345
|
+
id: 'DefectInEffectSync',
|
|
346
|
+
activate: () =>
|
|
347
|
+
Effect.sync(() => {
|
|
348
|
+
// This is a defect - a synchronous throw inside Effect.sync.
|
|
349
|
+
throw new Error('defect in Effect.sync');
|
|
350
|
+
}),
|
|
351
|
+
}),
|
|
352
|
+
Plugin.make,
|
|
353
|
+
)(),
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
357
|
+
yield* manager.add(testMeta.id);
|
|
358
|
+
yield* manager.enable(testMeta.id);
|
|
359
|
+
const error = yield* Effect.flip(manager.activate(DefectEvent));
|
|
360
|
+
|
|
361
|
+
// Verify the error was caught and propagated.
|
|
362
|
+
assert.strictEqual(error.message, 'defect in Effect.sync');
|
|
363
|
+
|
|
364
|
+
// Verify the error was logged with isDefect: true.
|
|
365
|
+
const defectLog = capturedErrors.find(
|
|
366
|
+
(entry) =>
|
|
367
|
+
entry.message?.includes('module failed to activate') &&
|
|
368
|
+
entry.context?.module === 'org.dxos.plugin.test.module.DefectInEffectSync',
|
|
369
|
+
);
|
|
370
|
+
assert.isNotNull(defectLog, 'Expected error log for defect');
|
|
371
|
+
assert.strictEqual(defectLog?.context?.isDefect, true, 'Expected isDefect to be true for synchronous throw');
|
|
372
|
+
|
|
373
|
+
removeProcessor();
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
it.effect('should catch and log defects when activate throws before returning Effect', () =>
|
|
378
|
+
Effect.gen(function* () {
|
|
379
|
+
const DefectEvent = ActivationEvent.make('org.dxos.test.defect-immediate');
|
|
380
|
+
const capturedErrors: LogEntry[] = [];
|
|
381
|
+
const removeProcessor = log.addProcessor((_config: LogConfig, entry: LogEntry) => {
|
|
382
|
+
if (entry.level === LogLevel.ERROR) {
|
|
383
|
+
capturedErrors.push(entry);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
plugins = [
|
|
388
|
+
Plugin.define(testMeta).pipe(
|
|
389
|
+
Plugin.addModule({
|
|
390
|
+
activatesOn: DefectEvent,
|
|
391
|
+
id: 'DefectImmediate',
|
|
392
|
+
activate: () => {
|
|
393
|
+
// This throws immediately before even returning an Effect.
|
|
394
|
+
// This is the most severe type of defect.
|
|
395
|
+
throw new Error('immediate throw before Effect');
|
|
396
|
+
return Effect.succeed(undefined);
|
|
397
|
+
},
|
|
398
|
+
}),
|
|
399
|
+
Plugin.make,
|
|
400
|
+
)(),
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
404
|
+
yield* manager.add(testMeta.id);
|
|
405
|
+
yield* manager.enable(testMeta.id);
|
|
406
|
+
const error = yield* Effect.flip(manager.activate(DefectEvent));
|
|
407
|
+
|
|
408
|
+
// Verify the error was caught and propagated.
|
|
409
|
+
assert.strictEqual(error.message, 'immediate throw before Effect');
|
|
410
|
+
|
|
411
|
+
// Verify the error was logged with isDefect: true.
|
|
412
|
+
const defectLog = capturedErrors.find(
|
|
413
|
+
(entry) =>
|
|
414
|
+
entry.message?.includes('module failed to activate') &&
|
|
415
|
+
entry.context?.module === 'org.dxos.plugin.test.module.DefectImmediate',
|
|
416
|
+
);
|
|
417
|
+
assert.isNotNull(defectLog, 'Expected error log for immediate defect');
|
|
418
|
+
assert.strictEqual(
|
|
419
|
+
defectLog?.context?.isDefect,
|
|
420
|
+
true,
|
|
421
|
+
'Expected isDefect to be true for immediate throw before Effect',
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
removeProcessor();
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
it.effect('should fire activation events', () =>
|
|
429
|
+
Effect.gen(function* () {
|
|
430
|
+
plugins = [
|
|
431
|
+
Plugin.define(testMeta).pipe(
|
|
432
|
+
Plugin.addModule({
|
|
433
|
+
id: 'Hello',
|
|
434
|
+
activatesOn: ActivationEvents.Startup,
|
|
435
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
436
|
+
}),
|
|
437
|
+
Plugin.addModule({
|
|
438
|
+
activatesOn: FailEvent,
|
|
439
|
+
id: 'Fail',
|
|
440
|
+
activate: () => Effect.fail(new Error('test')),
|
|
441
|
+
}),
|
|
442
|
+
Plugin.make,
|
|
443
|
+
)(),
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
447
|
+
const activating = yield* Effect.makeLatch(false);
|
|
448
|
+
const activated = yield* Effect.makeLatch(false);
|
|
449
|
+
const error = yield* Effect.makeLatch(false);
|
|
450
|
+
|
|
451
|
+
const activationFiber = PubSub.subscribe(manager.activation).pipe(
|
|
452
|
+
Effect.flatMap((queue) =>
|
|
453
|
+
Queue.take(queue).pipe(
|
|
454
|
+
Effect.flatMap(({ state }) =>
|
|
455
|
+
Match.value(state).pipe(
|
|
456
|
+
Match.when('activating', () => activating.open),
|
|
457
|
+
Match.when('activated', () => activated.open),
|
|
458
|
+
Match.when('error', () => error.open),
|
|
459
|
+
Match.orElse(() => Effect.succeed(undefined)),
|
|
460
|
+
),
|
|
461
|
+
),
|
|
462
|
+
Effect.forever,
|
|
463
|
+
),
|
|
464
|
+
),
|
|
465
|
+
Effect.scoped,
|
|
466
|
+
Effect.runFork,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
yield* manager.add(testMeta.id);
|
|
470
|
+
yield* manager.enable(testMeta.id);
|
|
471
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
472
|
+
yield* activating.await;
|
|
473
|
+
yield* activated.await;
|
|
474
|
+
|
|
475
|
+
const activating2 = yield* Effect.makeLatch(false);
|
|
476
|
+
const activationFiber2 = PubSub.subscribe(manager.activation).pipe(
|
|
477
|
+
Effect.flatMap((queue) =>
|
|
478
|
+
Queue.take(queue).pipe(
|
|
479
|
+
Effect.flatMap(({ state }) =>
|
|
480
|
+
Match.value(state).pipe(
|
|
481
|
+
Match.when('activating', () => activating2.open),
|
|
482
|
+
Match.orElse(() => Effect.succeed(undefined)),
|
|
483
|
+
),
|
|
484
|
+
),
|
|
485
|
+
Effect.forever,
|
|
486
|
+
),
|
|
487
|
+
),
|
|
488
|
+
Effect.scoped,
|
|
489
|
+
Effect.runFork,
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
yield* manager.activate(FailEvent).pipe(Effect.catchAll(() => Effect.succeed(false)));
|
|
493
|
+
yield* activating2.await;
|
|
494
|
+
yield* error.await;
|
|
495
|
+
yield* Fiber.interrupt(activationFiber);
|
|
496
|
+
yield* Fiber.interrupt(activationFiber2);
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
it.effect('should be able to reset an activation event', () =>
|
|
501
|
+
Effect.gen(function* () {
|
|
502
|
+
let count = 0;
|
|
503
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
504
|
+
Plugin.addModule({
|
|
505
|
+
id: 'Hello',
|
|
506
|
+
activatesOn: ActivationEvents.Startup,
|
|
507
|
+
activate: () => {
|
|
508
|
+
count++;
|
|
509
|
+
return Effect.succeed(Capability.contributes(String, { string: 'hello' }));
|
|
510
|
+
},
|
|
511
|
+
}),
|
|
512
|
+
Plugin.make,
|
|
513
|
+
);
|
|
514
|
+
const testPlugin = Test();
|
|
515
|
+
plugins = [testPlugin];
|
|
516
|
+
|
|
517
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
518
|
+
|
|
519
|
+
{
|
|
520
|
+
yield* manager.add(testMeta.id);
|
|
521
|
+
yield* manager.enable(testMeta.id);
|
|
522
|
+
const result = yield* manager.activate(ActivationEvents.Startup);
|
|
523
|
+
assert.isTrue(result);
|
|
524
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
525
|
+
assert.strictEqual(count, 1);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
{
|
|
529
|
+
const result = yield* manager.activate(ActivationEvents.Startup);
|
|
530
|
+
assert.isFalse(result);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
{
|
|
534
|
+
const result = yield* manager.reset(ActivationEvents.Startup);
|
|
535
|
+
assert.isTrue(result);
|
|
536
|
+
assert.strictEqual(count, 2);
|
|
537
|
+
}
|
|
538
|
+
}),
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
it.effect('should not fire an unknown event', () =>
|
|
542
|
+
Effect.gen(function* () {
|
|
543
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
544
|
+
const UnknownEvent = ActivationEvent.make('unknown');
|
|
545
|
+
const result = yield* manager.activate(UnknownEvent);
|
|
546
|
+
assert.isFalse(result);
|
|
547
|
+
}),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
it.effect('should be able to fire custom activation events', () =>
|
|
551
|
+
Effect.gen(function* () {
|
|
552
|
+
const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
|
|
553
|
+
Plugin.addModule({
|
|
554
|
+
activatesOn: CountEvent,
|
|
555
|
+
id: 'Plugin1',
|
|
556
|
+
activate: () => Effect.succeed([Capability.contributes(Number, { number: 1 })]),
|
|
557
|
+
}),
|
|
558
|
+
Plugin.make,
|
|
559
|
+
);
|
|
560
|
+
const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
|
|
561
|
+
Plugin.addModule({
|
|
562
|
+
activatesOn: CountEvent,
|
|
563
|
+
id: 'Plugin2',
|
|
564
|
+
activate: () => Effect.succeed([Capability.contributes(Number, { number: 2 })]),
|
|
565
|
+
}),
|
|
566
|
+
Plugin.make,
|
|
567
|
+
);
|
|
568
|
+
const Plugin3 = Plugin.define({ id: 'org.dxos.test.plugin-3', name: 'Plugin 3' }).pipe(
|
|
569
|
+
Plugin.addModule({
|
|
570
|
+
activatesOn: CountEvent,
|
|
571
|
+
id: 'Plugin3',
|
|
572
|
+
activate: () => Effect.succeed([Capability.contributes(Number, { number: 3 })]),
|
|
573
|
+
}),
|
|
574
|
+
Plugin.make,
|
|
575
|
+
);
|
|
576
|
+
const plugin1 = Plugin1();
|
|
577
|
+
const plugin2 = Plugin2();
|
|
578
|
+
const plugin3 = Plugin3();
|
|
579
|
+
plugins = [plugin1, plugin2, plugin3];
|
|
580
|
+
|
|
581
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
582
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
583
|
+
assert.strictEqual(manager.capabilities.getAll(Number).length, 0);
|
|
584
|
+
|
|
585
|
+
yield* manager.add(Plugin1.meta.id);
|
|
586
|
+
yield* manager.enable(Plugin1.meta.id);
|
|
587
|
+
yield* manager.activate(CountEvent);
|
|
588
|
+
assert.deepStrictEqual(manager.getActive(), [plugin1.modules[0].id]);
|
|
589
|
+
assert.strictEqual(manager.capabilities.getAll(Number).length, 1);
|
|
590
|
+
|
|
591
|
+
yield* manager.add(Plugin2.meta.id);
|
|
592
|
+
yield* manager.enable(Plugin2.meta.id);
|
|
593
|
+
yield* manager.activate(CountEvent);
|
|
594
|
+
assert.deepStrictEqual(manager.getActive(), [plugin1.modules[0].id, plugin2.modules[0].id]);
|
|
595
|
+
assert.strictEqual(manager.capabilities.getAll(Number).length, 2);
|
|
596
|
+
|
|
597
|
+
yield* manager.add(Plugin3.meta.id);
|
|
598
|
+
yield* manager.enable(Plugin3.meta.id);
|
|
599
|
+
yield* manager.activate(CountEvent);
|
|
600
|
+
assert.deepStrictEqual(manager.getActive(), [
|
|
601
|
+
plugin1.modules[0].id,
|
|
602
|
+
plugin2.modules[0].id,
|
|
603
|
+
plugin3.modules[0].id,
|
|
604
|
+
]);
|
|
605
|
+
assert.strictEqual(manager.capabilities.getAll(Number).length, 3);
|
|
606
|
+
}),
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
it.effect('should only activate modules after all activatation events have been fired', () =>
|
|
610
|
+
Effect.gen(function* () {
|
|
611
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
612
|
+
Plugin.addModule({
|
|
613
|
+
activatesOn: ActivationEvent.allOf(ActivationEvents.Startup, CountEvent),
|
|
614
|
+
id: 'Hello',
|
|
615
|
+
activate: () => {
|
|
616
|
+
return Effect.succeed(Capability.contributes(String, { string: 'hello' }));
|
|
617
|
+
},
|
|
618
|
+
}),
|
|
619
|
+
Plugin.make,
|
|
620
|
+
);
|
|
621
|
+
const testPlugin = Test();
|
|
622
|
+
plugins = [testPlugin];
|
|
623
|
+
|
|
624
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
625
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
626
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 0);
|
|
627
|
+
|
|
628
|
+
yield* manager.add(testMeta.id);
|
|
629
|
+
yield* manager.enable(testMeta.id);
|
|
630
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
631
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
632
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 0);
|
|
633
|
+
|
|
634
|
+
yield* manager.activate(CountEvent);
|
|
635
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
636
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
637
|
+
}),
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
it.effect('should only activate modules once when multiple activatation events have been fired', () =>
|
|
641
|
+
Effect.gen(function* () {
|
|
642
|
+
let count = 0;
|
|
643
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
644
|
+
Plugin.addModule({
|
|
645
|
+
id: 'Hello',
|
|
646
|
+
activatesOn: ActivationEvent.oneOf(ActivationEvents.Startup, CountEvent),
|
|
647
|
+
activate: () => {
|
|
648
|
+
count++;
|
|
649
|
+
return Effect.succeed(Capability.contributes(String, { string: 'hello' }));
|
|
650
|
+
},
|
|
651
|
+
}),
|
|
652
|
+
Plugin.make,
|
|
653
|
+
);
|
|
654
|
+
const testPlugin = Test();
|
|
655
|
+
plugins = [testPlugin];
|
|
656
|
+
|
|
657
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
658
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
659
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 0);
|
|
660
|
+
assert.strictEqual(count, 0);
|
|
661
|
+
|
|
662
|
+
yield* manager.add(testMeta.id);
|
|
663
|
+
yield* manager.enable(testMeta.id);
|
|
664
|
+
yield* manager.activate(CountEvent);
|
|
665
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
666
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
667
|
+
assert.strictEqual(count, 1);
|
|
668
|
+
|
|
669
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
670
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
671
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
672
|
+
assert.strictEqual(count, 1);
|
|
673
|
+
}),
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
it.effect('should be able to disable and re-enable an active plugin', () =>
|
|
677
|
+
Effect.gen(function* () {
|
|
678
|
+
const state = { total: 0 };
|
|
679
|
+
const computeTotal = (capabilityManager: CapabilityManager.CapabilityManager) => {
|
|
680
|
+
const numbers = capabilityManager.getAll(Number);
|
|
681
|
+
state.total = numbers.reduce((acc: number, n: { number: number }) => acc + n.number, 0);
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
const Count = Plugin.define({ id: 'org.dxos.test.count', name: 'Count' }).pipe(
|
|
685
|
+
Plugin.addModule({
|
|
686
|
+
id: 'Count',
|
|
687
|
+
activatesOn: ActivationEvents.Startup,
|
|
688
|
+
firesBeforeActivation: [CountEvent],
|
|
689
|
+
activate: Effect.fnUntraced(function* () {
|
|
690
|
+
const capabilityManager = yield* Capability.Service;
|
|
691
|
+
computeTotal(capabilityManager);
|
|
692
|
+
return Capability.contributes(Total, state);
|
|
693
|
+
}),
|
|
694
|
+
}),
|
|
695
|
+
Plugin.make,
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
699
|
+
Plugin.addModule({
|
|
700
|
+
activatesOn: CountEvent,
|
|
701
|
+
id: 'Test1',
|
|
702
|
+
activate: () => Effect.succeed(Capability.contributes(Number, { number: 1 })),
|
|
703
|
+
}),
|
|
704
|
+
Plugin.addModule({
|
|
705
|
+
id: 'Test2',
|
|
706
|
+
activatesOn: CountEvent,
|
|
707
|
+
activate: () => Effect.succeed(Capability.contributes(Number, { number: 2 })),
|
|
708
|
+
}),
|
|
709
|
+
Plugin.addModule({
|
|
710
|
+
id: 'Test3',
|
|
711
|
+
activatesOn: CountEvent,
|
|
712
|
+
activate: () => Effect.succeed(Capability.contributes(Number, { number: 3 })),
|
|
713
|
+
}),
|
|
714
|
+
Plugin.make,
|
|
715
|
+
);
|
|
716
|
+
const countPlugin = Count();
|
|
717
|
+
const testPlugin = Test();
|
|
718
|
+
plugins = [countPlugin, testPlugin];
|
|
719
|
+
|
|
720
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
721
|
+
{
|
|
722
|
+
yield* manager.add(Test.meta.id);
|
|
723
|
+
yield* manager.enable(Test.meta.id);
|
|
724
|
+
yield* manager.add(Count.meta.id);
|
|
725
|
+
yield* manager.enable(Count.meta.id);
|
|
726
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
727
|
+
assert.deepStrictEqual(manager.getActive(), [
|
|
728
|
+
...testPlugin.modules.map((m) => m.id),
|
|
729
|
+
countPlugin.modules[0].id,
|
|
730
|
+
]);
|
|
731
|
+
assert.deepStrictEqual(manager.getPendingReset(), []);
|
|
732
|
+
|
|
733
|
+
const totals = manager.capabilities.getAll(Total);
|
|
734
|
+
assert.strictEqual(totals.length, 1);
|
|
735
|
+
assert.strictEqual(totals[0].total, 6);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
{
|
|
739
|
+
yield* manager.disable(Test.meta.id);
|
|
740
|
+
assert.deepStrictEqual(manager.getActive(), [countPlugin.modules[0].id]);
|
|
741
|
+
assert.deepStrictEqual(manager.getPendingReset(), []);
|
|
742
|
+
|
|
743
|
+
const totals = manager.capabilities.getAll(Total);
|
|
744
|
+
assert.strictEqual(totals.length, 1);
|
|
745
|
+
// Total doesn't change because it is not reactive.
|
|
746
|
+
assert.strictEqual(totals[0].total, 6);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
{
|
|
750
|
+
yield* manager.enable(Test.meta.id);
|
|
751
|
+
assert.deepStrictEqual(manager.getActive(), [
|
|
752
|
+
countPlugin.modules[0].id,
|
|
753
|
+
...testPlugin.modules.map((m) => m.id),
|
|
754
|
+
]);
|
|
755
|
+
assert.deepStrictEqual(manager.getPendingReset(), []);
|
|
756
|
+
|
|
757
|
+
const totals = manager.capabilities.getAll(Total);
|
|
758
|
+
assert.strictEqual(totals.length, 1);
|
|
759
|
+
assert.strictEqual(totals[0].total, 6);
|
|
760
|
+
}
|
|
761
|
+
}),
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
it.effect('should be reactive', () =>
|
|
765
|
+
Effect.gen(function* () {
|
|
766
|
+
const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
|
|
767
|
+
Plugin.addModule({
|
|
768
|
+
activatesOn: CountEvent,
|
|
769
|
+
id: 'Plugin1',
|
|
770
|
+
activate: () => Effect.succeed([Capability.contributes(Number, { number: 1 })]),
|
|
771
|
+
}),
|
|
772
|
+
Plugin.make,
|
|
773
|
+
);
|
|
774
|
+
const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
|
|
775
|
+
Plugin.addModule({
|
|
776
|
+
activatesOn: CountEvent,
|
|
777
|
+
id: 'Plugin2',
|
|
778
|
+
activate: () => Effect.succeed([Capability.contributes(Number, { number: 2 })]),
|
|
779
|
+
}),
|
|
780
|
+
Plugin.make,
|
|
781
|
+
);
|
|
782
|
+
const Plugin3 = Plugin.define({ id: 'org.dxos.test.plugin-3', name: 'Plugin 3' }).pipe(
|
|
783
|
+
Plugin.addModule({
|
|
784
|
+
activatesOn: CountEvent,
|
|
785
|
+
id: 'Plugin3',
|
|
786
|
+
activate: () => Effect.succeed([Capability.contributes(Number, { number: 3 })]),
|
|
787
|
+
}),
|
|
788
|
+
Plugin.make,
|
|
789
|
+
);
|
|
790
|
+
plugins = [Plugin1(), Plugin2(), Plugin3()];
|
|
791
|
+
|
|
792
|
+
const registry = Registry.make();
|
|
793
|
+
const manager = PluginManager.make({ pluginLoader, registry });
|
|
794
|
+
using pluginUpdates = atomCounter(registry, manager.plugins);
|
|
795
|
+
using enabledUpdates = atomCounter(registry, manager.enabled);
|
|
796
|
+
using modulesUpdates = atomCounter(registry, manager.modules);
|
|
797
|
+
using activeUpdates = atomCounter(registry, manager.active);
|
|
798
|
+
using eventsFiredUpdates = atomCounter(registry, manager.eventsFired);
|
|
799
|
+
using pendingResetUpdates = atomCounter(registry, manager.pendingReset);
|
|
800
|
+
assert.strictEqual(pluginUpdates.count, 0);
|
|
801
|
+
assert.strictEqual(enabledUpdates.count, 0);
|
|
802
|
+
assert.strictEqual(modulesUpdates.count, 0);
|
|
803
|
+
assert.strictEqual(activeUpdates.count, 0);
|
|
804
|
+
assert.strictEqual(eventsFiredUpdates.count, 0);
|
|
805
|
+
assert.strictEqual(pendingResetUpdates.count, 0);
|
|
806
|
+
|
|
807
|
+
yield* manager.add(Plugin1.meta.id);
|
|
808
|
+
yield* manager.enable(Plugin1.meta.id);
|
|
809
|
+
assert.strictEqual(pluginUpdates.count, 1);
|
|
810
|
+
assert.strictEqual(enabledUpdates.count, 1);
|
|
811
|
+
assert.strictEqual(modulesUpdates.count, 1);
|
|
812
|
+
assert.strictEqual(activeUpdates.count, 0);
|
|
813
|
+
assert.strictEqual(eventsFiredUpdates.count, 0);
|
|
814
|
+
assert.strictEqual(pendingResetUpdates.count, 0);
|
|
815
|
+
|
|
816
|
+
yield* manager.activate(CountEvent);
|
|
817
|
+
assert.strictEqual(pluginUpdates.count, 1);
|
|
818
|
+
assert.strictEqual(enabledUpdates.count, 1);
|
|
819
|
+
assert.strictEqual(modulesUpdates.count, 1);
|
|
820
|
+
assert.strictEqual(activeUpdates.count, 1);
|
|
821
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
822
|
+
assert.strictEqual(pendingResetUpdates.count, 0);
|
|
823
|
+
|
|
824
|
+
yield* manager.add(Plugin2.meta.id);
|
|
825
|
+
yield* manager.enable(Plugin2.meta.id);
|
|
826
|
+
assert.strictEqual(pluginUpdates.count, 2);
|
|
827
|
+
assert.strictEqual(enabledUpdates.count, 2);
|
|
828
|
+
assert.strictEqual(modulesUpdates.count, 2);
|
|
829
|
+
assert.strictEqual(activeUpdates.count, 2);
|
|
830
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
831
|
+
assert.strictEqual(pendingResetUpdates.count, 2);
|
|
832
|
+
|
|
833
|
+
yield* manager.activate(CountEvent);
|
|
834
|
+
assert.strictEqual(pluginUpdates.count, 2);
|
|
835
|
+
assert.strictEqual(enabledUpdates.count, 2);
|
|
836
|
+
assert.strictEqual(modulesUpdates.count, 2);
|
|
837
|
+
assert.strictEqual(activeUpdates.count, 2);
|
|
838
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
839
|
+
assert.strictEqual(pendingResetUpdates.count, 2);
|
|
840
|
+
|
|
841
|
+
yield* manager.add(Plugin3.meta.id);
|
|
842
|
+
yield* manager.enable(Plugin3.meta.id);
|
|
843
|
+
assert.strictEqual(pluginUpdates.count, 3);
|
|
844
|
+
assert.strictEqual(enabledUpdates.count, 3);
|
|
845
|
+
assert.strictEqual(modulesUpdates.count, 3);
|
|
846
|
+
assert.strictEqual(activeUpdates.count, 3);
|
|
847
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
848
|
+
assert.strictEqual(pendingResetUpdates.count, 4);
|
|
849
|
+
|
|
850
|
+
yield* manager.reset(CountEvent);
|
|
851
|
+
assert.strictEqual(pluginUpdates.count, 3);
|
|
852
|
+
assert.strictEqual(enabledUpdates.count, 3);
|
|
853
|
+
assert.strictEqual(modulesUpdates.count, 3);
|
|
854
|
+
// Starts at 3, plus deactivates 3, plus activates 3.
|
|
855
|
+
assert.strictEqual(activeUpdates.count, 9);
|
|
856
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
857
|
+
assert.strictEqual(pendingResetUpdates.count, 4);
|
|
858
|
+
|
|
859
|
+
yield* manager.disable(Plugin1.meta.id);
|
|
860
|
+
assert.strictEqual(pluginUpdates.count, 3);
|
|
861
|
+
assert.strictEqual(enabledUpdates.count, 4);
|
|
862
|
+
assert.strictEqual(modulesUpdates.count, 4);
|
|
863
|
+
assert.strictEqual(activeUpdates.count, 10);
|
|
864
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
865
|
+
assert.strictEqual(pendingResetUpdates.count, 4);
|
|
866
|
+
|
|
867
|
+
yield* manager.remove(Plugin1.meta.id);
|
|
868
|
+
assert.strictEqual(pluginUpdates.count, 4);
|
|
869
|
+
assert.strictEqual(enabledUpdates.count, 4);
|
|
870
|
+
assert.strictEqual(modulesUpdates.count, 4);
|
|
871
|
+
assert.strictEqual(activeUpdates.count, 10);
|
|
872
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
873
|
+
assert.strictEqual(pendingResetUpdates.count, 4);
|
|
874
|
+
|
|
875
|
+
yield* manager.reset(CountEvent);
|
|
876
|
+
assert.strictEqual(pluginUpdates.count, 4);
|
|
877
|
+
assert.strictEqual(enabledUpdates.count, 4);
|
|
878
|
+
assert.strictEqual(modulesUpdates.count, 4);
|
|
879
|
+
// Starts at 10, plus deactivates 2, plus activates 2.
|
|
880
|
+
assert.strictEqual(activeUpdates.count, 14);
|
|
881
|
+
assert.strictEqual(eventsFiredUpdates.count, 1);
|
|
882
|
+
assert.strictEqual(pendingResetUpdates.count, 4);
|
|
883
|
+
}),
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
it.effect('should log a warning when a module takes too long to activate', () =>
|
|
887
|
+
Effect.gen(function* () {
|
|
888
|
+
const capturedWarnings: LogEntry[] = [];
|
|
889
|
+
const removeProcessor = log.addProcessor((_config: LogConfig, entry: LogEntry) => {
|
|
890
|
+
if (entry.level === LogLevel.WARN) {
|
|
891
|
+
capturedWarnings.push(entry);
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
const SlowEvent = ActivationEvent.make('org.dxos.test.slow');
|
|
896
|
+
const SlowPlugin = Plugin.define({ id: 'org.dxos.test.slow-plugin', name: 'Slow Plugin' }).pipe(
|
|
897
|
+
Plugin.addModule({
|
|
898
|
+
id: 'SlowModule',
|
|
899
|
+
activatesOn: SlowEvent,
|
|
900
|
+
activate: Effect.fnUntraced(function* () {
|
|
901
|
+
// Simulate a slow activation that takes 15 seconds.
|
|
902
|
+
yield* Effect.sleep(Duration.seconds(15));
|
|
903
|
+
return Capability.contributes(String, { string: 'slow' });
|
|
904
|
+
}),
|
|
905
|
+
}),
|
|
906
|
+
Plugin.make,
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
const slowPlugin = SlowPlugin();
|
|
910
|
+
plugins = [slowPlugin];
|
|
911
|
+
|
|
912
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
913
|
+
yield* manager.add(SlowPlugin.meta.id);
|
|
914
|
+
yield* manager.enable(SlowPlugin.meta.id);
|
|
915
|
+
|
|
916
|
+
// Fork the activation so we can control time with TestClock.
|
|
917
|
+
const activationFiber = yield* Effect.fork(manager.activate(SlowEvent));
|
|
918
|
+
|
|
919
|
+
// Advance time past the 10 second warning threshold.
|
|
920
|
+
yield* TestClock.adjust(Duration.seconds(11));
|
|
921
|
+
|
|
922
|
+
// Check that the warning was logged.
|
|
923
|
+
assert.isTrue(
|
|
924
|
+
capturedWarnings.some((entry) => entry.message?.includes('module is taking a long time to activate')),
|
|
925
|
+
'Expected a warning about slow module activation',
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
// Advance time to let the module finish activating.
|
|
929
|
+
yield* TestClock.adjust(Duration.seconds(5));
|
|
930
|
+
yield* Fiber.join(activationFiber);
|
|
931
|
+
|
|
932
|
+
removeProcessor();
|
|
933
|
+
}),
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
it.effect('should prevent concurrent loads of the same module via semaphore', () =>
|
|
937
|
+
Effect.gen(function* () {
|
|
938
|
+
// Two different events that both can trigger the same module.
|
|
939
|
+
const EventA = ActivationEvent.make('org.dxos.test.event-a');
|
|
940
|
+
const EventB = ActivationEvent.make('org.dxos.test.event-b');
|
|
941
|
+
|
|
942
|
+
let activateCallCount = 0;
|
|
943
|
+
const ConcurrentPlugin = Plugin.define({ id: 'org.dxos.test.concurrent-plugin', name: 'Concurrent Plugin' }).pipe(
|
|
944
|
+
Plugin.addModule({
|
|
945
|
+
id: 'ConcurrentModule',
|
|
946
|
+
// Module activates on either event - this allows two different events to race.
|
|
947
|
+
activatesOn: ActivationEvent.oneOf(EventA, EventB),
|
|
948
|
+
activate: Effect.fnUntraced(function* () {
|
|
949
|
+
activateCallCount++;
|
|
950
|
+
// Simulate slow activation to create window for race condition.
|
|
951
|
+
yield* Effect.sleep(Duration.seconds(5));
|
|
952
|
+
return Capability.contributes(String, { string: 'concurrent' });
|
|
953
|
+
}),
|
|
954
|
+
}),
|
|
955
|
+
Plugin.make,
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
const concurrentPlugin = ConcurrentPlugin();
|
|
959
|
+
plugins = [concurrentPlugin];
|
|
960
|
+
|
|
961
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
962
|
+
yield* manager.add(ConcurrentPlugin.meta.id);
|
|
963
|
+
yield* manager.enable(ConcurrentPlugin.meta.id);
|
|
964
|
+
|
|
965
|
+
// Fork two concurrent activations with DIFFERENT events.
|
|
966
|
+
// Both events trigger the same module, so both will try to call _loadModule.
|
|
967
|
+
// Without the semaphore, both would start loading the same module.
|
|
968
|
+
const fiber1 = yield* Effect.fork(manager.activate(EventA));
|
|
969
|
+
const fiber2 = yield* Effect.fork(manager.activate(EventB));
|
|
970
|
+
|
|
971
|
+
// Advance time to let both activations complete.
|
|
972
|
+
yield* TestClock.adjust(Duration.seconds(6));
|
|
973
|
+
|
|
974
|
+
yield* Fiber.join(fiber1);
|
|
975
|
+
yield* Fiber.join(fiber2);
|
|
976
|
+
|
|
977
|
+
// The semaphore should ensure the module's activate function is only called once,
|
|
978
|
+
// even when two different events race to load the same module.
|
|
979
|
+
assert.strictEqual(activateCallCount, 1, 'module activate should only be called once due to semaphore');
|
|
980
|
+
|
|
981
|
+
// Verify the capability was contributed.
|
|
982
|
+
const strings = manager.capabilities.getAll(String);
|
|
983
|
+
assert.isTrue(strings.length >= 1, 'capability should be contributed');
|
|
984
|
+
assert.strictEqual(strings[0].string, 'concurrent');
|
|
985
|
+
}),
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
it.effect('should deactivate all active modules on shutdown', () =>
|
|
989
|
+
Effect.gen(function* () {
|
|
990
|
+
const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
|
|
991
|
+
Plugin.addModule({
|
|
992
|
+
activatesOn: ActivationEvents.Startup,
|
|
993
|
+
id: 'Plugin1',
|
|
994
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
995
|
+
}),
|
|
996
|
+
Plugin.make,
|
|
997
|
+
);
|
|
998
|
+
const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
|
|
999
|
+
Plugin.addModule({
|
|
1000
|
+
activatesOn: ActivationEvents.Startup,
|
|
1001
|
+
id: 'Plugin2',
|
|
1002
|
+
activate: () => Effect.succeed(Capability.contributes(Number, { number: 42 })),
|
|
1003
|
+
}),
|
|
1004
|
+
Plugin.make,
|
|
1005
|
+
);
|
|
1006
|
+
const plugin1 = Plugin1();
|
|
1007
|
+
const plugin2 = Plugin2();
|
|
1008
|
+
plugins = [plugin1, plugin2];
|
|
1009
|
+
|
|
1010
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1011
|
+
yield* manager.add(Plugin1.meta.id);
|
|
1012
|
+
yield* manager.enable(Plugin1.meta.id);
|
|
1013
|
+
yield* manager.add(Plugin2.meta.id);
|
|
1014
|
+
yield* manager.enable(Plugin2.meta.id);
|
|
1015
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1016
|
+
assert.strictEqual(manager.getActive().length, 2);
|
|
1017
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
1018
|
+
assert.strictEqual(manager.capabilities.getAll(Number).length, 1);
|
|
1019
|
+
|
|
1020
|
+
const result = yield* manager.shutdown();
|
|
1021
|
+
assert.isTrue(result);
|
|
1022
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
1023
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 0);
|
|
1024
|
+
assert.strictEqual(manager.capabilities.getAll(Number).length, 0);
|
|
1025
|
+
}),
|
|
1026
|
+
);
|
|
1027
|
+
|
|
1028
|
+
it.effect('should run capability deactivate hooks during shutdown', () =>
|
|
1029
|
+
Effect.gen(function* () {
|
|
1030
|
+
let deactivated = false;
|
|
1031
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
1032
|
+
Plugin.addModule({
|
|
1033
|
+
id: 'WithDeactivate',
|
|
1034
|
+
activatesOn: ActivationEvents.Startup,
|
|
1035
|
+
activate: () =>
|
|
1036
|
+
Effect.succeed(
|
|
1037
|
+
Capability.contributes(String, { string: 'hello' }, () =>
|
|
1038
|
+
Effect.sync(() => {
|
|
1039
|
+
deactivated = true;
|
|
1040
|
+
}),
|
|
1041
|
+
),
|
|
1042
|
+
),
|
|
1043
|
+
}),
|
|
1044
|
+
Plugin.make,
|
|
1045
|
+
);
|
|
1046
|
+
const testPlugin = Test();
|
|
1047
|
+
plugins = [testPlugin];
|
|
1048
|
+
|
|
1049
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1050
|
+
yield* manager.add(testMeta.id);
|
|
1051
|
+
yield* manager.enable(testMeta.id);
|
|
1052
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1053
|
+
assert.isFalse(deactivated);
|
|
1054
|
+
|
|
1055
|
+
yield* manager.shutdown();
|
|
1056
|
+
assert.isTrue(deactivated);
|
|
1057
|
+
}),
|
|
1058
|
+
);
|
|
1059
|
+
|
|
1060
|
+
it.effect('should deactivate modules in reverse activation order during shutdown', () =>
|
|
1061
|
+
Effect.gen(function* () {
|
|
1062
|
+
const deactivationOrder: string[] = [];
|
|
1063
|
+
const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
|
|
1064
|
+
Plugin.addModule({
|
|
1065
|
+
activatesOn: ActivationEvents.Startup,
|
|
1066
|
+
id: 'First',
|
|
1067
|
+
activate: () =>
|
|
1068
|
+
Effect.succeed(
|
|
1069
|
+
Capability.contributes(String, { string: 'first' }, () =>
|
|
1070
|
+
Effect.sync(() => {
|
|
1071
|
+
deactivationOrder.push('First');
|
|
1072
|
+
}),
|
|
1073
|
+
),
|
|
1074
|
+
),
|
|
1075
|
+
}),
|
|
1076
|
+
Plugin.make,
|
|
1077
|
+
);
|
|
1078
|
+
const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
|
|
1079
|
+
Plugin.addModule({
|
|
1080
|
+
activatesOn: ActivationEvents.Startup,
|
|
1081
|
+
id: 'Second',
|
|
1082
|
+
activate: () =>
|
|
1083
|
+
Effect.succeed(
|
|
1084
|
+
Capability.contributes(Number, { number: 2 }, () =>
|
|
1085
|
+
Effect.sync(() => {
|
|
1086
|
+
deactivationOrder.push('Second');
|
|
1087
|
+
}),
|
|
1088
|
+
),
|
|
1089
|
+
),
|
|
1090
|
+
}),
|
|
1091
|
+
Plugin.make,
|
|
1092
|
+
);
|
|
1093
|
+
plugins = [Plugin1(), Plugin2()];
|
|
1094
|
+
|
|
1095
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1096
|
+
yield* manager.add(Plugin1.meta.id);
|
|
1097
|
+
yield* manager.enable(Plugin1.meta.id);
|
|
1098
|
+
yield* manager.add(Plugin2.meta.id);
|
|
1099
|
+
yield* manager.enable(Plugin2.meta.id);
|
|
1100
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1101
|
+
|
|
1102
|
+
yield* manager.shutdown();
|
|
1103
|
+
assert.deepStrictEqual(deactivationOrder, ['Second', 'First']);
|
|
1104
|
+
}),
|
|
1105
|
+
);
|
|
1106
|
+
|
|
1107
|
+
it.effect('should clear lifecycle bookkeeping during shutdown', () =>
|
|
1108
|
+
Effect.gen(function* () {
|
|
1109
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
1110
|
+
Plugin.addModule({
|
|
1111
|
+
id: 'Hello',
|
|
1112
|
+
activatesOn: ActivationEvents.Startup,
|
|
1113
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
1114
|
+
}),
|
|
1115
|
+
Plugin.make,
|
|
1116
|
+
);
|
|
1117
|
+
const testPlugin = Test();
|
|
1118
|
+
plugins = [testPlugin];
|
|
1119
|
+
|
|
1120
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1121
|
+
yield* manager.add(testMeta.id);
|
|
1122
|
+
yield* manager.enable(testMeta.id);
|
|
1123
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1124
|
+
assert.isTrue(manager.getEventsFired().length > 0);
|
|
1125
|
+
|
|
1126
|
+
yield* manager.shutdown();
|
|
1127
|
+
assert.deepStrictEqual(manager.getEventsFired(), []);
|
|
1128
|
+
assert.deepStrictEqual(manager.getPendingReset(), []);
|
|
1129
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
1130
|
+
}),
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
it.effect('should interrupt in-flight activation during shutdown', () =>
|
|
1134
|
+
Effect.gen(function* () {
|
|
1135
|
+
const activationStarted = yield* Effect.makeLatch(false);
|
|
1136
|
+
const allowActivationToComplete = yield* Effect.makeLatch(false);
|
|
1137
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
1138
|
+
Plugin.addModule({
|
|
1139
|
+
id: 'Hello',
|
|
1140
|
+
activatesOn: ActivationEvents.Startup,
|
|
1141
|
+
activate: () =>
|
|
1142
|
+
Effect.gen(function* () {
|
|
1143
|
+
yield* activationStarted.open;
|
|
1144
|
+
yield* allowActivationToComplete.await;
|
|
1145
|
+
return Capability.contributes(String, { string: 'hello' });
|
|
1146
|
+
}),
|
|
1147
|
+
}),
|
|
1148
|
+
Plugin.make,
|
|
1149
|
+
);
|
|
1150
|
+
const testPlugin = Test();
|
|
1151
|
+
plugins = [testPlugin];
|
|
1152
|
+
|
|
1153
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1154
|
+
yield* manager.add(testMeta.id);
|
|
1155
|
+
yield* manager.enable(testMeta.id);
|
|
1156
|
+
|
|
1157
|
+
const activationFiber = yield* Effect.fork(manager.activate(ActivationEvents.Startup));
|
|
1158
|
+
yield* activationStarted.await;
|
|
1159
|
+
|
|
1160
|
+
const shutdownFiber = yield* Effect.fork(manager.shutdown());
|
|
1161
|
+
yield* allowActivationToComplete.open;
|
|
1162
|
+
|
|
1163
|
+
const shutdownResult = yield* Fiber.join(shutdownFiber);
|
|
1164
|
+
const activationExit = yield* Fiber.await(activationFiber);
|
|
1165
|
+
|
|
1166
|
+
assert.isTrue(shutdownResult);
|
|
1167
|
+
assert.isTrue(Exit.isFailure(activationExit));
|
|
1168
|
+
if (Exit.isFailure(activationExit)) {
|
|
1169
|
+
assert.isTrue(Cause.isInterruptedOnly(activationExit.cause));
|
|
1170
|
+
}
|
|
1171
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 0);
|
|
1172
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
1173
|
+
assert.deepStrictEqual(manager.getEventsFired(), []);
|
|
1174
|
+
}),
|
|
1175
|
+
);
|
|
1176
|
+
|
|
1177
|
+
it.effect('should preserve plugins, core, enabled, and modules after shutdown', () =>
|
|
1178
|
+
Effect.gen(function* () {
|
|
1179
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
1180
|
+
Plugin.addModule({
|
|
1181
|
+
id: 'Hello',
|
|
1182
|
+
activatesOn: ActivationEvents.Startup,
|
|
1183
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
1184
|
+
}),
|
|
1185
|
+
Plugin.make,
|
|
1186
|
+
);
|
|
1187
|
+
const testPlugin = Test();
|
|
1188
|
+
plugins = [testPlugin];
|
|
1189
|
+
|
|
1190
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1191
|
+
yield* manager.add(testMeta.id);
|
|
1192
|
+
yield* manager.enable(testMeta.id);
|
|
1193
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1194
|
+
|
|
1195
|
+
const pluginsBefore = manager.getPlugins();
|
|
1196
|
+
const coreBefore = manager.getCore();
|
|
1197
|
+
const enabledBefore = manager.getEnabled();
|
|
1198
|
+
const modulesBefore = manager.getModules();
|
|
1199
|
+
|
|
1200
|
+
yield* manager.shutdown();
|
|
1201
|
+
|
|
1202
|
+
assert.deepStrictEqual(manager.getPlugins(), pluginsBefore);
|
|
1203
|
+
assert.deepStrictEqual(manager.getCore(), coreBefore);
|
|
1204
|
+
assert.deepStrictEqual(manager.getEnabled(), enabledBefore);
|
|
1205
|
+
assert.deepStrictEqual(manager.getModules(), modulesBefore);
|
|
1206
|
+
}),
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1209
|
+
it.effect('should allow re-activation after shutdown', () =>
|
|
1210
|
+
Effect.gen(function* () {
|
|
1211
|
+
let activateCount = 0;
|
|
1212
|
+
const Test = Plugin.define(testMeta).pipe(
|
|
1213
|
+
Plugin.addModule({
|
|
1214
|
+
id: 'Hello',
|
|
1215
|
+
activatesOn: ActivationEvents.Startup,
|
|
1216
|
+
activate: () => {
|
|
1217
|
+
activateCount++;
|
|
1218
|
+
return Effect.succeed(Capability.contributes(String, { string: 'hello' }));
|
|
1219
|
+
},
|
|
1220
|
+
}),
|
|
1221
|
+
Plugin.make,
|
|
1222
|
+
);
|
|
1223
|
+
const testPlugin = Test();
|
|
1224
|
+
plugins = [testPlugin];
|
|
1225
|
+
|
|
1226
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1227
|
+
yield* manager.add(testMeta.id);
|
|
1228
|
+
yield* manager.enable(testMeta.id);
|
|
1229
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1230
|
+
assert.strictEqual(activateCount, 1);
|
|
1231
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
1232
|
+
|
|
1233
|
+
yield* manager.shutdown();
|
|
1234
|
+
assert.deepStrictEqual(manager.getActive(), []);
|
|
1235
|
+
|
|
1236
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1237
|
+
assert.strictEqual(activateCount, 2);
|
|
1238
|
+
assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
|
|
1239
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
1240
|
+
}),
|
|
1241
|
+
);
|
|
1242
|
+
|
|
1243
|
+
describe('Plugin.lazy', () => {
|
|
1244
|
+
const lazyMeta = { id: 'org.dxos.plugin.lazy', name: 'Lazy' };
|
|
1245
|
+
|
|
1246
|
+
it('exposes meta synchronously without invoking the loader', () => {
|
|
1247
|
+
let loaderCalls = 0;
|
|
1248
|
+
const Real = Plugin.make(Plugin.define<void>(lazyMeta));
|
|
1249
|
+
const LazyTest = Plugin.lazy(lazyMeta, () => {
|
|
1250
|
+
loaderCalls++;
|
|
1251
|
+
return Promise.resolve({ default: Real });
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
assert.strictEqual(LazyTest.meta.id, lazyMeta.id);
|
|
1255
|
+
assert.strictEqual(LazyTest.meta.name, 'Lazy');
|
|
1256
|
+
assert.strictEqual(loaderCalls, 0);
|
|
1257
|
+
|
|
1258
|
+
const stub = LazyTest();
|
|
1259
|
+
assert.strictEqual(stub.meta.id, lazyMeta.id);
|
|
1260
|
+
assert.deepStrictEqual([...stub.modules], []);
|
|
1261
|
+
assert.isTrue(Plugin.isLazy(stub));
|
|
1262
|
+
assert.strictEqual(loaderCalls, 0);
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
it.effect('resolves the loader on enable and registers the real plugin modules', () =>
|
|
1266
|
+
Effect.gen(function* () {
|
|
1267
|
+
let loaderCalls = 0;
|
|
1268
|
+
const Real = Plugin.define(lazyMeta).pipe(
|
|
1269
|
+
Plugin.addModule({
|
|
1270
|
+
id: 'Hello',
|
|
1271
|
+
activatesOn: ActivationEvents.Startup,
|
|
1272
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
1273
|
+
}),
|
|
1274
|
+
Plugin.make,
|
|
1275
|
+
);
|
|
1276
|
+
const LazyTest = Plugin.lazy(lazyMeta, () => {
|
|
1277
|
+
loaderCalls++;
|
|
1278
|
+
return Promise.resolve({ default: Real });
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
const lazyStub = LazyTest();
|
|
1282
|
+
plugins = [lazyStub];
|
|
1283
|
+
|
|
1284
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1285
|
+
yield* manager.add(lazyMeta.id);
|
|
1286
|
+
// Loader has not been invoked yet — only meta is exposed.
|
|
1287
|
+
assert.strictEqual(loaderCalls, 0);
|
|
1288
|
+
assert.deepStrictEqual(manager.getModules(), []);
|
|
1289
|
+
|
|
1290
|
+
yield* manager.enable(lazyMeta.id);
|
|
1291
|
+
assert.strictEqual(loaderCalls, 1);
|
|
1292
|
+
// After enable the registered plugin should be the real one (not the stub),
|
|
1293
|
+
// and its modules should be registered with the manager.
|
|
1294
|
+
const registered = manager.getPlugins().find((p) => p.meta.id === lazyMeta.id);
|
|
1295
|
+
assert.isDefined(registered);
|
|
1296
|
+
assert.isFalse(Plugin.isLazy(registered!));
|
|
1297
|
+
assert.strictEqual(registered!.modules.length, 1);
|
|
1298
|
+
|
|
1299
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1300
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
1301
|
+
}),
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
it.effect('does not invoke the loader if the plugin is never enabled', () =>
|
|
1305
|
+
Effect.gen(function* () {
|
|
1306
|
+
let loaderCalls = 0;
|
|
1307
|
+
const Real = Plugin.make(Plugin.define<void>(lazyMeta));
|
|
1308
|
+
const LazyTest = Plugin.lazy(lazyMeta, () => {
|
|
1309
|
+
loaderCalls++;
|
|
1310
|
+
return Promise.resolve({ default: Real });
|
|
1311
|
+
});
|
|
1312
|
+
const lazyStub = LazyTest();
|
|
1313
|
+
plugins = [lazyStub];
|
|
1314
|
+
|
|
1315
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1316
|
+
yield* manager.add(lazyMeta.id);
|
|
1317
|
+
|
|
1318
|
+
// Activate an event that has no listeners — the lazy plugin must not load.
|
|
1319
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1320
|
+
assert.strictEqual(loaderCalls, 0);
|
|
1321
|
+
}),
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
it.effect('forwards factory options to the real plugin factory', () =>
|
|
1325
|
+
Effect.gen(function* () {
|
|
1326
|
+
type Opts = { greeting: string };
|
|
1327
|
+
const RealFactory = (opts: Opts) =>
|
|
1328
|
+
Plugin.define(lazyMeta).pipe(
|
|
1329
|
+
Plugin.addModule({
|
|
1330
|
+
id: 'Hello',
|
|
1331
|
+
activatesOn: ActivationEvents.Startup,
|
|
1332
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: opts.greeting })),
|
|
1333
|
+
}),
|
|
1334
|
+
Plugin.make,
|
|
1335
|
+
)(undefined as void);
|
|
1336
|
+
|
|
1337
|
+
const RealFactoryWithMeta = Object.assign(RealFactory, { meta: lazyMeta });
|
|
1338
|
+
|
|
1339
|
+
const LazyTest = Plugin.lazy<Opts>(lazyMeta, () => Promise.resolve({ default: RealFactoryWithMeta }));
|
|
1340
|
+
const lazyStub = LazyTest({ greeting: 'hola' });
|
|
1341
|
+
plugins = [lazyStub];
|
|
1342
|
+
|
|
1343
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1344
|
+
yield* manager.add(lazyMeta.id);
|
|
1345
|
+
yield* manager.enable(lazyMeta.id);
|
|
1346
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
1347
|
+
|
|
1348
|
+
const all = manager.capabilities.getAll(String);
|
|
1349
|
+
assert.strictEqual(all.length, 1);
|
|
1350
|
+
assert.strictEqual(all[0].string, 'hola');
|
|
1351
|
+
}),
|
|
1352
|
+
);
|
|
1353
|
+
|
|
1354
|
+
it.effect('wraps loader rejections in a descriptive error', () =>
|
|
1355
|
+
Effect.gen(function* () {
|
|
1356
|
+
const LazyTest = Plugin.lazy(lazyMeta, () =>
|
|
1357
|
+
Promise.reject<{ default: Plugin.PluginFactory }>(new Error('boom')),
|
|
1358
|
+
);
|
|
1359
|
+
const lazyStub = LazyTest();
|
|
1360
|
+
plugins = [lazyStub];
|
|
1361
|
+
|
|
1362
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1363
|
+
yield* manager.add(lazyMeta.id);
|
|
1364
|
+
|
|
1365
|
+
const exit = yield* Effect.exit(manager.enable(lazyMeta.id));
|
|
1366
|
+
assert.isTrue(Exit.isFailure(exit));
|
|
1367
|
+
if (Exit.isFailure(exit)) {
|
|
1368
|
+
const failure = Cause.failureOption(exit.cause);
|
|
1369
|
+
assert.isTrue(failure._tag === 'Some');
|
|
1370
|
+
if (failure._tag === 'Some') {
|
|
1371
|
+
assert.isTrue(Plugin.LazyPluginError.is(failure.value));
|
|
1372
|
+
assert.strictEqual((failure.value as Plugin.LazyPluginError).context.id, lazyMeta.id);
|
|
1373
|
+
assert.strictEqual((failure.value as Plugin.LazyPluginError).context.reason, 'load-failed');
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}),
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
it.effect('publishes a lazy:<id> error message when resolution fails', () =>
|
|
1380
|
+
Effect.gen(function* () {
|
|
1381
|
+
const LazyTest = Plugin.lazy(lazyMeta, () =>
|
|
1382
|
+
Promise.reject<{ default: Plugin.PluginFactory }>(new Error('boom')),
|
|
1383
|
+
);
|
|
1384
|
+
const lazyStub = LazyTest();
|
|
1385
|
+
plugins = [lazyStub];
|
|
1386
|
+
|
|
1387
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1388
|
+
// Subscribe first so we don't miss the activating/error pair.
|
|
1389
|
+
const queue = yield* PubSub.subscribe(manager.activation);
|
|
1390
|
+
yield* manager.add(lazyMeta.id);
|
|
1391
|
+
yield* Effect.exit(manager.enable(lazyMeta.id));
|
|
1392
|
+
const messages = yield* Queue.takeAll(queue);
|
|
1393
|
+
|
|
1394
|
+
const errorMessage = [...messages].find((m) => m.module === `lazy:${lazyMeta.id}` && m.state === 'error');
|
|
1395
|
+
assert.isDefined(errorMessage);
|
|
1396
|
+
assert.isDefined(errorMessage!.error);
|
|
1397
|
+
}).pipe(Effect.scoped),
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
it.effect('coalesces concurrent lazy resolutions of the same plugin id', () =>
|
|
1401
|
+
Effect.gen(function* () {
|
|
1402
|
+
let factoryCalls = 0;
|
|
1403
|
+
const Real = (() => {
|
|
1404
|
+
const inner = Plugin.make(
|
|
1405
|
+
Plugin.define<void>(lazyMeta).pipe(
|
|
1406
|
+
Plugin.addModule({
|
|
1407
|
+
id: 'Hello',
|
|
1408
|
+
activatesOn: ActivationEvents.Startup,
|
|
1409
|
+
activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
|
|
1410
|
+
}),
|
|
1411
|
+
),
|
|
1412
|
+
);
|
|
1413
|
+
const factory = (() => {
|
|
1414
|
+
factoryCalls++;
|
|
1415
|
+
return inner();
|
|
1416
|
+
}) as Plugin.PluginFactory;
|
|
1417
|
+
return Object.assign(factory, { meta: lazyMeta });
|
|
1418
|
+
})();
|
|
1419
|
+
const LazyTest = Plugin.lazy(lazyMeta, () => Promise.resolve({ default: Real }));
|
|
1420
|
+
const lazyStub = LazyTest();
|
|
1421
|
+
// `manager.enable(id)` is implicitly called twice — once from the
|
|
1422
|
+
// constructor's core/enabled chain, once from our explicit call. With
|
|
1423
|
+
// coalescing, the underlying factory should still run exactly once.
|
|
1424
|
+
plugins = [lazyStub];
|
|
1425
|
+
const manager = PluginManager.make({ pluginLoader, plugins, core: [lazyMeta.id] });
|
|
1426
|
+
yield* manager.enable(lazyMeta.id);
|
|
1427
|
+
assert.strictEqual(factoryCalls, 1);
|
|
1428
|
+
}),
|
|
1429
|
+
);
|
|
1430
|
+
|
|
1431
|
+
it.effect('fails with a tagged error when the factory output is not a Plugin', () =>
|
|
1432
|
+
Effect.gen(function* () {
|
|
1433
|
+
const BadFactory = Object.assign(() => ({ not: 'a plugin' }) as any, { meta: lazyMeta });
|
|
1434
|
+
const LazyTest = Plugin.lazy(lazyMeta, () => Promise.resolve({ default: BadFactory }));
|
|
1435
|
+
const lazyStub = LazyTest();
|
|
1436
|
+
plugins = [lazyStub];
|
|
1437
|
+
|
|
1438
|
+
const manager = PluginManager.make({ pluginLoader });
|
|
1439
|
+
yield* manager.add(lazyMeta.id);
|
|
1440
|
+
|
|
1441
|
+
const exit = yield* Effect.exit(manager.enable(lazyMeta.id));
|
|
1442
|
+
assert.isTrue(Exit.isFailure(exit));
|
|
1443
|
+
if (Exit.isFailure(exit)) {
|
|
1444
|
+
const failure = Cause.failureOption(exit.cause);
|
|
1445
|
+
assert.isTrue(failure._tag === 'Some');
|
|
1446
|
+
if (failure._tag === 'Some') {
|
|
1447
|
+
assert.isTrue(Plugin.LazyPluginError.is(failure.value));
|
|
1448
|
+
assert.strictEqual((failure.value as Plugin.LazyPluginError).context.reason, 'invalid-plugin');
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}),
|
|
1452
|
+
);
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
describe('timeouts and failure tracking', () => {
|
|
1456
|
+
// Atom subscriptions fire synchronously when the registry's `_set` runs,
|
|
1457
|
+
// even from a forked fiber on the default runtime. Wrapping in
|
|
1458
|
+
// `Effect.async` lets a TestClock-driven test wait for state produced by
|
|
1459
|
+
// a background `_runForkedFiber` (e.g. the auto-disable triggered when a
|
|
1460
|
+
// module activation times out) without relying on real-time `sleep`.
|
|
1461
|
+
const waitFor = <T>(registry: Registry.Registry, atom: Atom.Atom<T>, predicate: (value: T) => boolean) =>
|
|
1462
|
+
Effect.async<void>((resume) => {
|
|
1463
|
+
if (predicate(registry.get(atom))) {
|
|
1464
|
+
resume(Effect.void);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
let resolved = false;
|
|
1468
|
+
const dispose = registry.subscribe(atom, () => {
|
|
1469
|
+
if (!resolved && predicate(registry.get(atom))) {
|
|
1470
|
+
resolved = true;
|
|
1471
|
+
dispose();
|
|
1472
|
+
resume(Effect.void);
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
return Effect.sync(() => {
|
|
1476
|
+
if (!resolved) {
|
|
1477
|
+
dispose();
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
it.effect('records and auto-disables a plugin whose module exceeds the activation timeout', () =>
|
|
1483
|
+
Effect.gen(function* () {
|
|
1484
|
+
const SlowEvent = ActivationEvent.make('org.dxos.test.activation-timeout');
|
|
1485
|
+
const SlowPlugin = Plugin.define({ id: 'org.dxos.test.slow-activation', name: 'Slow Activation' }).pipe(
|
|
1486
|
+
Plugin.addModule({
|
|
1487
|
+
id: 'Slow',
|
|
1488
|
+
activatesOn: SlowEvent,
|
|
1489
|
+
activate: Effect.fnUntraced(function* () {
|
|
1490
|
+
yield* Effect.sleep(Duration.seconds(60));
|
|
1491
|
+
return Capability.contributes(String, { string: 'never' });
|
|
1492
|
+
}),
|
|
1493
|
+
}),
|
|
1494
|
+
Plugin.make,
|
|
1495
|
+
);
|
|
1496
|
+
plugins = [SlowPlugin()];
|
|
1497
|
+
|
|
1498
|
+
const registry = Registry.make();
|
|
1499
|
+
const manager = PluginManager.make({
|
|
1500
|
+
pluginLoader,
|
|
1501
|
+
registry,
|
|
1502
|
+
activationTimeout: Duration.seconds(2),
|
|
1503
|
+
});
|
|
1504
|
+
yield* manager.add(SlowPlugin.meta.id);
|
|
1505
|
+
yield* manager.enable(SlowPlugin.meta.id);
|
|
1506
|
+
|
|
1507
|
+
const fiber = yield* Effect.fork(manager.activate(SlowEvent));
|
|
1508
|
+
// Push past the 2s activation timeout. The forked module fiber is on
|
|
1509
|
+
// TestClock too, so the timeout fires deterministically.
|
|
1510
|
+
yield* TestClock.adjust(Duration.seconds(3));
|
|
1511
|
+
const exit = yield* Fiber.await(fiber);
|
|
1512
|
+
assert.isTrue(Exit.isFailure(exit));
|
|
1513
|
+
|
|
1514
|
+
const failed = manager.getFailed();
|
|
1515
|
+
assert.strictEqual(failed.length, 1);
|
|
1516
|
+
assert.strictEqual(failed[0].id, SlowPlugin.meta.id);
|
|
1517
|
+
assert.strictEqual(failed[0].phase, 'activation');
|
|
1518
|
+
assert.strictEqual(failed[0].reason, 'timeout');
|
|
1519
|
+
|
|
1520
|
+
// Auto-disable runs in a forked fiber on the default runtime; wait for
|
|
1521
|
+
// the `enabled` atom to settle to the disabled state.
|
|
1522
|
+
yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(SlowPlugin.meta.id));
|
|
1523
|
+
assert.deepStrictEqual(manager.getEnabled(), []);
|
|
1524
|
+
}),
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
it.effect('records and auto-disables a lazy plugin whose loader exceeds the load timeout', () =>
|
|
1528
|
+
Effect.gen(function* () {
|
|
1529
|
+
const lazyMeta = { id: 'org.dxos.test.slow-load', name: 'Slow Load' };
|
|
1530
|
+
// The dynamic import never resolves; the manager's load timeout should
|
|
1531
|
+
// surface this as a `LazyPluginError` whose `cause` is `PluginTimeoutError`.
|
|
1532
|
+
const LazyTest = Plugin.lazy(lazyMeta, () => new Promise<{ default: Plugin.PluginFactory }>(() => {}));
|
|
1533
|
+
plugins = [LazyTest()];
|
|
1534
|
+
|
|
1535
|
+
const registry = Registry.make();
|
|
1536
|
+
const manager = PluginManager.make({
|
|
1537
|
+
pluginLoader,
|
|
1538
|
+
registry,
|
|
1539
|
+
loadTimeout: Duration.seconds(1),
|
|
1540
|
+
});
|
|
1541
|
+
yield* manager.add(lazyMeta.id);
|
|
1542
|
+
|
|
1543
|
+
const enableFiber = yield* Effect.fork(manager.enable(lazyMeta.id));
|
|
1544
|
+
yield* TestClock.adjust(Duration.seconds(2));
|
|
1545
|
+
const exit = yield* Fiber.await(enableFiber);
|
|
1546
|
+
assert.isTrue(Exit.isFailure(exit));
|
|
1547
|
+
|
|
1548
|
+
// The wrapped `LazyPluginError` carries the timeout error as its cause.
|
|
1549
|
+
if (Exit.isFailure(exit)) {
|
|
1550
|
+
const failure = Cause.failureOption(exit.cause);
|
|
1551
|
+
if (failure._tag === 'Some') {
|
|
1552
|
+
assert.isTrue(Plugin.LazyPluginError.is(failure.value));
|
|
1553
|
+
const lazyError = failure.value as Plugin.LazyPluginError;
|
|
1554
|
+
assert.isTrue(PluginManager.PluginTimeoutError.is(lazyError.cause as Error));
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
const failed = manager.getFailed();
|
|
1559
|
+
assert.strictEqual(failed.length, 1);
|
|
1560
|
+
assert.strictEqual(failed[0].id, lazyMeta.id);
|
|
1561
|
+
assert.strictEqual(failed[0].phase, 'load');
|
|
1562
|
+
assert.strictEqual(failed[0].reason, 'timeout');
|
|
1563
|
+
|
|
1564
|
+
// The plugin was added to `enabled` before the lazy resolution failed,
|
|
1565
|
+
// so the auto-disable fork should clear it.
|
|
1566
|
+
yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(lazyMeta.id));
|
|
1567
|
+
}),
|
|
1568
|
+
);
|
|
1569
|
+
|
|
1570
|
+
it.effect('records non-timeout activation errors as reason: error', () =>
|
|
1571
|
+
Effect.gen(function* () {
|
|
1572
|
+
const FailingEvent = ActivationEvent.make('org.dxos.test.activation-error');
|
|
1573
|
+
const FailingPlugin = Plugin.define({ id: 'org.dxos.test.failing', name: 'Failing' }).pipe(
|
|
1574
|
+
Plugin.addModule({
|
|
1575
|
+
id: 'Boom',
|
|
1576
|
+
activatesOn: FailingEvent,
|
|
1577
|
+
activate: () => Effect.fail(new Error('boom')),
|
|
1578
|
+
}),
|
|
1579
|
+
Plugin.make,
|
|
1580
|
+
);
|
|
1581
|
+
plugins = [FailingPlugin()];
|
|
1582
|
+
|
|
1583
|
+
const registry = Registry.make();
|
|
1584
|
+
const manager = PluginManager.make({ pluginLoader, registry });
|
|
1585
|
+
yield* manager.add(FailingPlugin.meta.id);
|
|
1586
|
+
yield* manager.enable(FailingPlugin.meta.id);
|
|
1587
|
+
|
|
1588
|
+
const exit = yield* Effect.exit(manager.activate(FailingEvent));
|
|
1589
|
+
assert.isTrue(Exit.isFailure(exit));
|
|
1590
|
+
|
|
1591
|
+
const failed = manager.getFailed();
|
|
1592
|
+
assert.strictEqual(failed.length, 1);
|
|
1593
|
+
assert.strictEqual(failed[0].reason, 'error');
|
|
1594
|
+
assert.strictEqual(failed[0].error.message, 'boom');
|
|
1595
|
+
|
|
1596
|
+
yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(FailingPlugin.meta.id));
|
|
1597
|
+
}),
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
it.effect('does not auto-disable a core plugin even though the failure is recorded', () =>
|
|
1601
|
+
Effect.gen(function* () {
|
|
1602
|
+
const FailingEvent = ActivationEvent.make('org.dxos.test.core-fail');
|
|
1603
|
+
const CorePlugin = Plugin.define({ id: 'org.dxos.test.core', name: 'Core' }).pipe(
|
|
1604
|
+
Plugin.addModule({
|
|
1605
|
+
id: 'Boom',
|
|
1606
|
+
activatesOn: FailingEvent,
|
|
1607
|
+
activate: () => Effect.fail(new Error('boom')),
|
|
1608
|
+
}),
|
|
1609
|
+
Plugin.make,
|
|
1610
|
+
);
|
|
1611
|
+
const corePlugin = CorePlugin();
|
|
1612
|
+
plugins = [corePlugin];
|
|
1613
|
+
|
|
1614
|
+
const manager = PluginManager.make({
|
|
1615
|
+
pluginLoader,
|
|
1616
|
+
plugins: [corePlugin],
|
|
1617
|
+
core: [corePlugin.meta.id],
|
|
1618
|
+
});
|
|
1619
|
+
// Core is auto-enabled via the constructor's enable chain.
|
|
1620
|
+
const exit = yield* Effect.exit(manager.activate(FailingEvent));
|
|
1621
|
+
assert.isTrue(Exit.isFailure(exit));
|
|
1622
|
+
|
|
1623
|
+
assert.strictEqual(manager.getFailed().length, 1);
|
|
1624
|
+
// Core stays enabled; host opted into it being non-removable.
|
|
1625
|
+
assert.deepStrictEqual(manager.getEnabled(), [corePlugin.meta.id]);
|
|
1626
|
+
}),
|
|
1627
|
+
);
|
|
1628
|
+
|
|
1629
|
+
it.effect('clearFailure removes the failure record and re-enable starts fresh', () =>
|
|
1630
|
+
Effect.gen(function* () {
|
|
1631
|
+
let shouldFail = true;
|
|
1632
|
+
const Event = ActivationEvent.make('org.dxos.test.flaky');
|
|
1633
|
+
const FlakyPlugin = Plugin.define({ id: 'org.dxos.test.flaky', name: 'Flaky' }).pipe(
|
|
1634
|
+
Plugin.addModule({
|
|
1635
|
+
id: 'Maybe',
|
|
1636
|
+
activatesOn: Event,
|
|
1637
|
+
activate: () =>
|
|
1638
|
+
shouldFail
|
|
1639
|
+
? Effect.fail(new Error('first try'))
|
|
1640
|
+
: Effect.succeed(Capability.contributes(String, { string: 'ok' })),
|
|
1641
|
+
}),
|
|
1642
|
+
Plugin.make,
|
|
1643
|
+
);
|
|
1644
|
+
const flakyPlugin = FlakyPlugin();
|
|
1645
|
+
plugins = [flakyPlugin];
|
|
1646
|
+
|
|
1647
|
+
const registry = Registry.make();
|
|
1648
|
+
const manager = PluginManager.make({ pluginLoader, registry });
|
|
1649
|
+
yield* manager.add(flakyPlugin.meta.id);
|
|
1650
|
+
yield* manager.enable(flakyPlugin.meta.id);
|
|
1651
|
+
|
|
1652
|
+
yield* Effect.exit(manager.activate(Event));
|
|
1653
|
+
assert.strictEqual(manager.getFailed().length, 1);
|
|
1654
|
+
yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(flakyPlugin.meta.id));
|
|
1655
|
+
|
|
1656
|
+
// Calling `enable` again clears the prior failure record before
|
|
1657
|
+
// attempting resolution; verify the explicit API does too.
|
|
1658
|
+
assert.isTrue(manager.clearFailure(flakyPlugin.meta.id));
|
|
1659
|
+
assert.strictEqual(manager.getFailed().length, 0);
|
|
1660
|
+
assert.isFalse(manager.clearFailure(flakyPlugin.meta.id));
|
|
1661
|
+
|
|
1662
|
+
// Retry: enable + reset the activation event so the module re-runs.
|
|
1663
|
+
shouldFail = false;
|
|
1664
|
+
yield* manager.enable(flakyPlugin.meta.id);
|
|
1665
|
+
yield* manager.reset(Event);
|
|
1666
|
+
assert.strictEqual(manager.getFailed().length, 0);
|
|
1667
|
+
assert.strictEqual(manager.capabilities.getAll(String).length, 1);
|
|
1668
|
+
}),
|
|
1669
|
+
);
|
|
1670
|
+
});
|
|
1671
|
+
});
|