@dxos/app-framework 0.8.4-main.3eb6e50203 → 0.8.4-main.3fbcb4aa9b
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 +1 -3
- package/dist/lib/browser/{capability-DU35RXMD.mjs → capability-5RRH3WIB.mjs} +11 -10
- package/dist/lib/browser/capability-5RRH3WIB.mjs.map +7 -0
- package/dist/lib/browser/{capability-7LE5X2NH.mjs → capability-LUKGKUQH.mjs} +10 -9
- package/dist/lib/browser/{chunk-4DQMLMGU.mjs → chunk-23D4SJUE.mjs} +11 -13
- package/dist/lib/browser/{chunk-4DQMLMGU.mjs.map → chunk-23D4SJUE.mjs.map} +3 -3
- package/dist/lib/browser/{chunk-ZQO3UZ4R.mjs → chunk-3JWJXGLK.mjs} +6 -4
- 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-PKQT6C53.mjs → chunk-66IXTIVK.mjs} +3 -2
- 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-FHQTHCX7.mjs.map → chunk-FJ4765WW.mjs.map} +3 -3
- package/dist/lib/browser/chunk-FO3IYSLV.mjs +68 -0
- package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +7 -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-IGRQWMWW.mjs → chunk-WBHCSOBW.mjs} +18 -18
- package/dist/lib/browser/chunk-WBHCSOBW.mjs.map +7 -0
- package/dist/lib/browser/{chunk-KWP6PKIU.mjs → chunk-Z55LVAGN.mjs} +87 -19
- package/dist/lib/browser/chunk-Z55LVAGN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-EIFGKQSA.mjs → chunk-ZGJAZSNE.mjs} +9 -37
- package/dist/lib/browser/chunk-ZGJAZSNE.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +16 -29
- package/dist/lib/browser/cli/index.mjs.map +3 -3
- package/dist/lib/browser/common/activation-events.mjs +9 -8
- package/dist/lib/browser/common/capabilities.mjs +9 -8
- package/dist/lib/browser/core/activation-event.mjs +1 -1
- package/dist/lib/browser/core/capability.mjs +3 -1
- package/dist/lib/browser/core/plugin-manager.mjs +8 -4
- package/dist/lib/browser/core/plugin.mjs +14 -4
- package/dist/lib/browser/core/url-loader.mjs +24 -0
- package/dist/lib/browser/index.mjs +45 -24
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/{invoker-capability-7QW56NOM.mjs → invoker-capability-K4GHUFXF.mjs} +22 -14
- 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 +184 -45
- 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 +19 -20
- package/dist/lib/node-esm/{capability-OSVYGK4X.mjs → capability-FCGZVIEG.mjs} +10 -9
- package/dist/lib/{browser/capability-7LE5X2NH.mjs.map → node-esm/capability-FCGZVIEG.mjs.map} +1 -1
- package/dist/lib/node-esm/{capability-SR2EIZFP.mjs → capability-JOIQ2MQE.mjs} +11 -10
- package/dist/lib/node-esm/capability-JOIQ2MQE.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-UEWJDI2L.mjs → chunk-37Z53PXZ.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-UEWJDI2L.mjs.map → chunk-37Z53PXZ.mjs.map} +3 -3
- 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-DHSHRYIB.mjs → chunk-D347W3KO.mjs} +9 -37
- 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-DZ2XLAGI.mjs → chunk-HTBJU5FX.mjs} +87 -19
- 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-J2PHTRHC.mjs → chunk-SBS2YMPT.mjs} +11 -13
- package/dist/lib/node-esm/{chunk-J2PHTRHC.mjs.map → chunk-SBS2YMPT.mjs.map} +3 -3
- package/dist/lib/node-esm/{chunk-64IMLAS2.mjs → chunk-SDJ4B2LU.mjs} +6 -4
- 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-OQYHRZYF.mjs → chunk-WFSRZKBP.mjs} +18 -18
- 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-7OWSHPYK.mjs → chunk-XOCUANHO.mjs} +3 -2
- package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +7 -0
- package/dist/lib/node-esm/cli/index.mjs +16 -29
- package/dist/lib/node-esm/cli/index.mjs.map +3 -3
- package/dist/lib/node-esm/common/activation-events.mjs +9 -8
- package/dist/lib/node-esm/common/capabilities.mjs +9 -8
- package/dist/lib/node-esm/core/activation-event.mjs +1 -1
- package/dist/lib/node-esm/core/capability.mjs +3 -1
- package/dist/lib/node-esm/core/plugin-manager.mjs +8 -4
- package/dist/lib/node-esm/core/plugin.mjs +14 -4
- package/dist/lib/node-esm/core/url-loader.mjs +25 -0
- package/dist/lib/node-esm/index.mjs +45 -24
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/{invoker-capability-CK4AMF2R.mjs → invoker-capability-XEPW5LMJ.mjs} +22 -14
- 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 +184 -45
- 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 +19 -20
- 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/common/activation-events.d.ts +1 -1
- package/dist/types/src/common/activation-events.d.ts.map +1 -1
- package/dist/types/src/common/annotations.d.ts +1 -0
- package/dist/types/src/common/annotations.d.ts.map +1 -0
- package/dist/types/src/common/capabilities.d.ts +4 -8
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/common/operations.d.ts +8 -22
- package/dist/types/src/common/operations.d.ts.map +1 -1
- package/dist/types/src/core/activation-event.d.ts +5 -5
- package/dist/types/src/core/activation-event.d.ts.map +1 -1
- package/dist/types/src/core/capability-manager.d.ts +5 -0
- package/dist/types/src/core/capability-manager.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +14 -8
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts +30 -0
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -0
- package/dist/types/src/core/index.d.ts +6 -0
- package/dist/types/src/core/index.d.ts.map +1 -1
- package/dist/types/src/core/plugin-asset-cache.d.ts +71 -0
- package/dist/types/src/core/plugin-asset-cache.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager.d.ts +177 -4
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manifest.d.ts +101 -0
- package/dist/types/src/core/plugin-manifest.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manifest.test.d.ts +2 -0
- package/dist/types/src/core/plugin-manifest.test.d.ts.map +1 -0
- package/dist/types/src/core/plugin.d.ts +113 -7
- 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/plugin-operation/OperationPlugin.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/capability.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/capability.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/errors.d.ts +30 -3
- package/dist/types/src/plugin-operation/history/errors.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/types.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/types.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts +1 -1
- package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/invoker-capability.d.ts +1 -1
- package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/meta.d.ts.map +1 -1
- package/dist/types/src/plugin-operation/testing.d.ts +27 -77
- package/dist/types/src/plugin-operation/testing.d.ts.map +1 -1
- package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts.map +1 -1
- package/dist/types/src/plugin-runtime/capability.d.ts +1 -1
- package/dist/types/src/plugin-runtime/capability.d.ts.map +1 -1
- package/dist/types/src/plugin-runtime/meta.d.ts.map +1 -1
- package/dist/types/src/testing/harness.d.ts +67 -0
- package/dist/types/src/testing/harness.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/react.d.ts +27 -0
- package/dist/types/src/testing/react.d.ts.map +1 -0
- package/dist/types/src/testing/react.test.d.ts +2 -0
- package/dist/types/src/testing/react.test.d.ts.map +1 -0
- package/dist/types/src/testing/service.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/{App.d.ts → App/App.d.ts} +3 -2
- package/dist/types/src/ui/components/App/App.d.ts.map +1 -0
- package/dist/types/src/ui/components/{App.stories.d.ts → App/App.stories.d.ts} +6 -1
- 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/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/{PluginManagerProvider.d.ts → PluginManager/PluginManagerProvider.d.ts} +1 -1
- 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/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/{surface → Surface}/SurfaceInfo.d.ts.map +1 -1
- 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 → Surface}/context.d.ts.map +1 -1
- 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 +3 -3
- package/dist/types/src/ui/components/index.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/index.d.ts +0 -1
- package/dist/types/src/ui/hooks/index.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts +47 -8
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useSurface.d.ts +2 -2
- package/dist/types/src/ui/hooks/useSurface.d.ts.map +1 -1
- package/dist/types/src/ui/index.d.ts +0 -1
- package/dist/types/src/ui/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +34 -0
- package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/boot-loader/index.d.ts +2 -0
- package/dist/types/src/vite-plugin/boot-loader/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/boot-loader/loader.d.ts +51 -0
- package/dist/types/src/vite-plugin/boot-loader/loader.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/composer/index.d.ts +34 -0
- package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/import-map/index.d.ts +28 -0
- package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/index.d.ts +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 +16 -4
- package/package.json +58 -62
- package/src/cli/cli.ts +3 -3
- package/src/common/activation-events.ts +6 -6
- package/src/common/annotations.ts +3 -0
- package/src/common/capabilities.ts +18 -23
- package/src/common/operations.ts +7 -10
- package/src/context.ts +1 -1
- package/src/core/activation-event.ts +5 -2
- package/src/core/capability-manager.test.ts +1 -1
- package/src/core/capability-manager.ts +22 -1
- package/src/core/capability.ts +21 -10
- package/src/core/edge-registry-plugin-provider.ts +92 -0
- package/src/core/index.ts +6 -0
- package/src/core/plugin-asset-cache.ts +60 -0
- package/src/core/plugin-manager.test.ts +855 -29
- package/src/core/plugin-manager.ts +818 -179
- package/src/core/plugin-manifest.test.ts +75 -0
- package/src/core/plugin-manifest.ts +134 -0
- package/src/core/plugin.ts +145 -12
- 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/plugin-operation/OperationPlugin.ts +2 -3
- package/src/plugin-operation/history/capability.ts +1 -2
- package/src/plugin-operation/history/errors.ts +2 -6
- package/src/plugin-operation/history/history-tracker.test.ts +37 -43
- package/src/plugin-operation/history/history-tracker.ts +1 -2
- package/src/plugin-operation/history/types.ts +1 -1
- package/src/plugin-operation/history/undo-mapping.ts +1 -1
- package/src/plugin-operation/history/undo-registry.test.ts +3 -4
- package/src/plugin-operation/history/undo-registry.ts +1 -1
- package/src/plugin-operation/invoker-capability.ts +19 -4
- package/src/plugin-operation/meta.ts +2 -1
- package/src/plugin-operation/testing.ts +26 -45
- package/src/plugin-runtime/RuntimePlugin.ts +2 -3
- package/src/plugin-runtime/meta.ts +2 -1
- package/src/testing/harness.ts +229 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/react.test.tsx +48 -0
- package/src/testing/react.tsx +113 -0
- package/src/testing/service.ts +3 -3
- package/src/testing/withPluginManager.stories.tsx +1 -2
- package/src/testing/withPluginManager.tsx +40 -15
- package/src/ui/components/App/App.stories.tsx +88 -0
- package/src/ui/components/App/App.tsx +81 -0
- package/src/ui/components/App/index.ts +5 -0
- package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
- package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
- package/src/ui/components/Placeholder/index.ts +5 -0
- package/src/ui/components/{PluginManagerContext.stories.tsx → PluginManager/PluginManagerContext.stories.tsx} +15 -13
- package/src/ui/components/{PluginManagerProvider.ts → PluginManager/PluginManagerProvider.ts} +1 -1
- package/src/ui/components/PluginManager/index.ts +5 -0
- package/src/ui/components/{surface → Surface}/SurfaceComponent.stories.tsx +25 -23
- package/src/ui/components/Surface/SurfaceComponent.tsx +303 -0
- package/src/ui/components/{surface → Surface}/SurfaceInfo.tsx +1 -2
- package/src/ui/components/Surface/SurfaceProfilerContext.tsx +207 -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 +3 -3
- package/src/ui/hooks/index.ts +0 -1
- package/src/ui/hooks/useApp.test.tsx +159 -0
- package/src/ui/hooks/useApp.tsx +233 -24
- package/src/ui/hooks/useCapabilities.ts +1 -1
- package/src/ui/hooks/useLoading.tsx +14 -6
- package/src/ui/hooks/useSurface.ts +3 -3
- package/src/ui/index.ts +0 -1
- 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 +22 -1
- package/tsconfig.node.json +2 -4
- package/typedoc.json +2 -4
- package/vitest.config.ts +1 -1
- package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
- package/dist/lib/browser/capability-DU35RXMD.mjs.map +0 -7
- package/dist/lib/browser/chunk-EIFGKQSA.mjs.map +0 -7
- package/dist/lib/browser/chunk-FHQTHCX7.mjs +0 -8
- package/dist/lib/browser/chunk-IGRQWMWW.mjs.map +0 -7
- package/dist/lib/browser/chunk-KWP6PKIU.mjs.map +0 -7
- package/dist/lib/browser/chunk-NPUEVX42.mjs +0 -34
- package/dist/lib/browser/chunk-NPUEVX42.mjs.map +0 -7
- package/dist/lib/browser/chunk-PKQT6C53.mjs.map +0 -7
- package/dist/lib/browser/chunk-WGWEEBUV.mjs +0 -807
- package/dist/lib/browser/chunk-WGWEEBUV.mjs.map +0 -7
- package/dist/lib/browser/chunk-YAFEA4GV.mjs +0 -1
- package/dist/lib/browser/chunk-YZQ6NFG4.mjs +0 -781
- package/dist/lib/browser/chunk-YZQ6NFG4.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZQO3UZ4R.mjs.map +0 -7
- package/dist/lib/browser/invoker-capability-7QW56NOM.mjs.map +0 -7
- package/dist/lib/node-esm/capability-SR2EIZFP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-64IMLAS2.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-7OWSHPYK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-DHSHRYIB.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-DZ2XLAGI.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JAZVHID3.mjs +0 -35
- package/dist/lib/node-esm/chunk-JAZVHID3.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-OQYHRZYF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-VV4YHPQQ.mjs +0 -782
- package/dist/lib/node-esm/chunk-VV4YHPQQ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XHAKT6UD.mjs +0 -808
- package/dist/lib/node-esm/chunk-XHAKT6UD.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Z4TJPSMP.mjs +0 -2
- package/dist/lib/node-esm/invoker-capability-CK4AMF2R.mjs.map +0 -7
- package/dist/types/src/ui/components/App.d.ts.map +0 -1
- package/dist/types/src/ui/components/App.stories.d.ts.map +0 -1
- package/dist/types/src/ui/components/DefaultFallback.d.ts +0 -8
- package/dist/types/src/ui/components/DefaultFallback.d.ts.map +0 -1
- package/dist/types/src/ui/components/ErrorBoundary.d.ts +0 -30
- package/dist/types/src/ui/components/ErrorBoundary.d.ts.map +0 -1
- package/dist/types/src/ui/components/PluginManagerContext.stories.d.ts.map +0 -1
- package/dist/types/src/ui/components/PluginManagerProvider.d.ts.map +0 -1
- package/dist/types/src/ui/components/surface/SurfaceComponent.d.ts +0 -12
- package/dist/types/src/ui/components/surface/SurfaceComponent.d.ts.map +0 -1
- package/dist/types/src/ui/components/surface/SurfaceComponent.stories.d.ts.map +0 -1
- package/dist/types/src/ui/components/surface/index.d.ts +0 -5
- package/dist/types/src/ui/components/surface/index.d.ts.map +0 -1
- package/dist/types/src/ui/components/surface/types.d.ts +0 -94
- package/dist/types/src/ui/components/surface/types.d.ts.map +0 -1
- package/dist/types/src/ui/hooks/useOperationResolver.d.ts +0 -19
- package/dist/types/src/ui/hooks/useOperationResolver.d.ts.map +0 -1
- package/src/ui/components/App.stories.tsx +0 -62
- package/src/ui/components/App.tsx +0 -57
- package/src/ui/components/DefaultFallback.tsx +0 -26
- package/src/ui/components/ErrorBoundary.tsx +0 -56
- package/src/ui/components/surface/SurfaceComponent.tsx +0 -262
- package/src/ui/components/surface/index.ts +0 -8
- package/src/ui/components/surface/types.ts +0 -119
- package/src/ui/hooks/useOperationResolver.ts +0 -40
- /package/dist/lib/{node-esm/capability-OSVYGK4X.mjs.map → browser/capability-LUKGKUQH.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-YAFEA4GV.mjs.map → core/url-loader.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-Z4TJPSMP.mjs.map → core/url-loader.mjs.map} +0 -0
- /package/dist/types/src/ui/components/{PluginManagerContext.stories.d.ts → PluginManager/PluginManagerContext.stories.d.ts} +0 -0
- /package/dist/types/src/ui/components/{surface → Surface}/SurfaceComponent.stories.d.ts +0 -0
- /package/dist/types/src/ui/components/{surface → Surface}/SurfaceInfo.d.ts +0 -0
- /package/dist/types/src/ui/components/{surface → Surface}/context.d.ts +0 -0
- /package/src/ui/components/{surface → Surface}/context.ts +0 -0
|
@@ -15,12 +15,65 @@ import * as PubSub from 'effect/PubSub';
|
|
|
15
15
|
import * as Ref from 'effect/Ref';
|
|
16
16
|
|
|
17
17
|
import { runAndForwardErrors } from '@dxos/effect';
|
|
18
|
+
import { Performance } from '@dxos/effect';
|
|
19
|
+
import { BaseError } from '@dxos/errors';
|
|
18
20
|
import { log } from '@dxos/log';
|
|
19
21
|
|
|
20
22
|
import * as ActivationEvent from './activation-event';
|
|
21
23
|
import * as Capability from './capability';
|
|
22
24
|
import * as CapabilityManager from './capability-manager';
|
|
23
25
|
import * as Plugin from './plugin';
|
|
26
|
+
// Imported with a `PluginRegistry` alias because the unrelated `@effect-atom/atom-react`
|
|
27
|
+
// `Registry` is already imported above; from outside this file the namespace is
|
|
28
|
+
// re-exported as `Registry` via `./index.ts`.
|
|
29
|
+
import * as PluginRegistry from './registry';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tagged error for failures during the constructor-launched core/enabled
|
|
33
|
+
* `enable()` chain. Surfaces via {@link PluginManager.activate}'s wait on
|
|
34
|
+
* `_initialization` so a caller blocked on initialization gets a typed
|
|
35
|
+
* failure (with the original error preserved as `cause`) instead of an
|
|
36
|
+
* untyped `Error`.
|
|
37
|
+
*/
|
|
38
|
+
export class PluginInitializationError extends BaseError.extend(
|
|
39
|
+
'PluginInitializationError',
|
|
40
|
+
'Plugin manager initialization failed',
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Tagged error raised when a plugin exceeds its configured load or activation
|
|
45
|
+
* timeout. The plugin manager records the failure on the `failed` atom and
|
|
46
|
+
* auto-disables the plugin so that one stuck remote does not stall app boot.
|
|
47
|
+
* `context.id` is the plugin id, `context.phase` is `'load'` or `'activation'`.
|
|
48
|
+
*/
|
|
49
|
+
export class PluginTimeoutError extends BaseError.extend('PluginTimeoutError', 'Plugin operation timed out') {}
|
|
50
|
+
|
|
51
|
+
/** Phase of the plugin lifecycle in which the failure was observed. */
|
|
52
|
+
export type PluginFailurePhase = 'load' | 'activation';
|
|
53
|
+
|
|
54
|
+
/** Why the plugin entered a failed state. */
|
|
55
|
+
export type PluginFailureReason = 'timeout' | 'error';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Record of a plugin that failed to load or activate. Surfaced via the
|
|
59
|
+
* {@link PluginManager.failed} atom so registry / UI consumers can flag
|
|
60
|
+
* unhealthy plugins (e.g. a remote host that has gone offline) rather than
|
|
61
|
+
* leaving the app in a half-broken state.
|
|
62
|
+
*/
|
|
63
|
+
export type PluginFailure = {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly phase: PluginFailurePhase;
|
|
66
|
+
readonly reason: PluginFailureReason;
|
|
67
|
+
readonly error: Error;
|
|
68
|
+
/** `Date.now()` when the failure was recorded. */
|
|
69
|
+
readonly timestamp: number;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Default deadline for resolving a lazy plugin's dynamic import. */
|
|
73
|
+
const DEFAULT_LOAD_TIMEOUT = Duration.seconds(30);
|
|
74
|
+
|
|
75
|
+
/** Default deadline for a single module's `activate()` body. */
|
|
76
|
+
const DEFAULT_ACTIVATION_TIMEOUT = Duration.seconds(30);
|
|
24
77
|
|
|
25
78
|
/**
|
|
26
79
|
* Identifier denoting a Manager.
|
|
@@ -28,15 +81,63 @@ import * as Plugin from './plugin';
|
|
|
28
81
|
export const ManagerTypeId: unique symbol = Symbol.for('@dxos/app-framework/Manager');
|
|
29
82
|
export type ManagerTypeId = typeof ManagerTypeId;
|
|
30
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Loader result that carries optional metadata about how the plugin was sourced.
|
|
86
|
+
*
|
|
87
|
+
* `dev: true` marks a plugin as session-only and triggers shadow-on-id-collision
|
|
88
|
+
* inside the manager: if a plugin with the same id is already registered (a
|
|
89
|
+
* builtin, or a previously-installed plugin from the registry), the dev plugin
|
|
90
|
+
* temporarily takes over that id slot. The original is restored when the dev
|
|
91
|
+
* plugin is removed (or on page reload, since dev plugins aren't persisted).
|
|
92
|
+
*/
|
|
93
|
+
export type LoadedPlugin = {
|
|
94
|
+
plugin: Plugin.Plugin;
|
|
95
|
+
/** True when the plugin came from a dev source. See type doc for semantics. */
|
|
96
|
+
dev?: boolean;
|
|
97
|
+
};
|
|
98
|
+
|
|
31
99
|
export type ManagerOptions = {
|
|
32
|
-
pluginLoader: (id: string) => Effect.Effect<
|
|
100
|
+
pluginLoader: (id: string) => Effect.Effect<LoadedPlugin, Error>;
|
|
33
101
|
plugins?: Plugin.Plugin[];
|
|
34
102
|
core?: string[];
|
|
35
103
|
enabled?: string[];
|
|
36
104
|
registry?: Registry.Registry;
|
|
105
|
+
/**
|
|
106
|
+
* Backend for the plugin registry catalog. When omitted the manager exposes a
|
|
107
|
+
* no-op `pluginRegistry` (empty list, no versions endpoint). Implementations
|
|
108
|
+
* live in app-framework alongside the interface (e.g.
|
|
109
|
+
* `EdgeRegistryPluginProvider`); the host app instantiates one and passes it in.
|
|
110
|
+
*/
|
|
111
|
+
pluginRegistryProvider?: PluginRegistry.PluginProvider;
|
|
112
|
+
/**
|
|
113
|
+
* Hook called when a plugin is removed via {@link PluginManager.remove}. Used by the
|
|
114
|
+
* host app to clean up persisted state (e.g. evict offline-cached plugin assets).
|
|
115
|
+
* Failures are logged and swallowed; removal still succeeds even if the hook fails.
|
|
116
|
+
*/
|
|
117
|
+
onRemove?: (id: string) => Effect.Effect<void, unknown>;
|
|
118
|
+
/**
|
|
119
|
+
* Maximum time allowed for a lazy plugin's dynamic `import()` to resolve.
|
|
120
|
+
* Plugins that exceed this are flagged on the {@link PluginManager.failed}
|
|
121
|
+
* atom and auto-disabled so a stuck remote host can't stall app boot.
|
|
122
|
+
* Defaults to 30 seconds; pass `Duration.infinity` to disable.
|
|
123
|
+
*/
|
|
124
|
+
loadTimeout?: Duration.DurationInput;
|
|
125
|
+
/**
|
|
126
|
+
* Maximum time allowed for a single module's `activate()` Effect to settle.
|
|
127
|
+
* Modules that exceed this fail with {@link PluginTimeoutError}; the owning
|
|
128
|
+
* plugin is recorded on `failed` and auto-disabled. Defaults to 30 seconds;
|
|
129
|
+
* pass `Duration.infinity` to disable.
|
|
130
|
+
*/
|
|
131
|
+
activationTimeout?: Duration.DurationInput;
|
|
37
132
|
};
|
|
38
133
|
|
|
39
|
-
type ActivationMessage = {
|
|
134
|
+
export type ActivationMessage = {
|
|
135
|
+
event: string;
|
|
136
|
+
state: 'activating' | 'activated' | 'error';
|
|
137
|
+
/** Module ID when the message pertains to a specific module activation. */
|
|
138
|
+
module?: string;
|
|
139
|
+
error?: Error;
|
|
140
|
+
};
|
|
40
141
|
|
|
41
142
|
/**
|
|
42
143
|
* Interface for the Plugin Manager.
|
|
@@ -46,6 +147,13 @@ export interface PluginManager {
|
|
|
46
147
|
readonly activation: PubSub.PubSub<ActivationMessage>;
|
|
47
148
|
readonly capabilities: CapabilityManager.CapabilityManager;
|
|
48
149
|
readonly registry: Registry.Registry;
|
|
150
|
+
/**
|
|
151
|
+
* Cached registry catalog state plus pass-throughs for `listVersions` /
|
|
152
|
+
* `getPlugin`. Always present — the host supplies a `pluginRegistryProvider`
|
|
153
|
+
* via {@link ManagerOptions} for real backends, or it falls back to a no-op
|
|
154
|
+
* implementation that yields an empty catalog.
|
|
155
|
+
*/
|
|
156
|
+
readonly pluginRegistry: PluginRegistry.Manager;
|
|
49
157
|
|
|
50
158
|
readonly plugins: Atom.Atom<readonly Plugin.Plugin[]>;
|
|
51
159
|
readonly core: Atom.Atom<readonly string[]>;
|
|
@@ -54,6 +162,19 @@ export interface PluginManager {
|
|
|
54
162
|
readonly active: Atom.Atom<readonly string[]>;
|
|
55
163
|
readonly eventsFired: Atom.Atom<readonly string[]>;
|
|
56
164
|
readonly pendingReset: Atom.Atom<readonly string[]>;
|
|
165
|
+
/**
|
|
166
|
+
* Plugins that failed to load or activate. Subscribers (e.g. the registry
|
|
167
|
+
* UI) can use this to flag unhealthy entries; a plugin id appears here at
|
|
168
|
+
* most once with its most recent failure.
|
|
169
|
+
*/
|
|
170
|
+
readonly failed: Atom.Atom<readonly PluginFailure[]>;
|
|
171
|
+
/**
|
|
172
|
+
* Ids of currently-registered plugins that came from a dev source (loaded
|
|
173
|
+
* via {@link LoadedPlugin} with `dev: true`). Subscribers can use this to
|
|
174
|
+
* badge dev-overridden plugins or to derive the id of the active dev plugin
|
|
175
|
+
* for an "uninstall dev plugin" affordance.
|
|
176
|
+
*/
|
|
177
|
+
readonly devPluginIds: Atom.Atom<readonly string[]>;
|
|
57
178
|
|
|
58
179
|
getPlugins(): readonly Plugin.Plugin[];
|
|
59
180
|
getCore(): readonly string[];
|
|
@@ -62,10 +183,23 @@ export interface PluginManager {
|
|
|
62
183
|
getActive(): readonly string[];
|
|
63
184
|
getEventsFired(): readonly string[];
|
|
64
185
|
getPendingReset(): readonly string[];
|
|
186
|
+
getFailed(): readonly PluginFailure[];
|
|
187
|
+
getDevPluginIds(): readonly string[];
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Clears the failure record for a plugin so it can be retried. Returns
|
|
191
|
+
* whether a failure record existed and was removed.
|
|
192
|
+
*/
|
|
193
|
+
clearFailure(id: string): boolean;
|
|
65
194
|
|
|
66
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Loads a plugin via the plugin loader and registers it without enabling it.
|
|
197
|
+
* Returns the loaded plugin so callers can enable it by its canonical id
|
|
198
|
+
* (which may differ from the locator used to load it, e.g. URL loaders).
|
|
199
|
+
*/
|
|
200
|
+
add(id: string): Effect.Effect<Plugin.Plugin, Error>;
|
|
67
201
|
enable(id: string): Effect.Effect<boolean, Error>;
|
|
68
|
-
remove(id: string): boolean
|
|
202
|
+
remove(id: string): Effect.Effect<boolean, Error>;
|
|
69
203
|
disable(id: string): Effect.Effect<boolean, Error>;
|
|
70
204
|
// TODO(wittjosiah): Improve error typing.
|
|
71
205
|
activate(
|
|
@@ -74,6 +208,13 @@ export interface PluginManager {
|
|
|
74
208
|
): Effect.Effect<boolean, Error>;
|
|
75
209
|
deactivate(id: string): Effect.Effect<boolean, Error>;
|
|
76
210
|
reset(event: ActivationEvent.ActivationEvent | string): Effect.Effect<boolean, Error>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Shuts down the manager by deactivating all active modules in reverse activation order,
|
|
214
|
+
* clearing all capabilities, and resetting lifecycle bookkeeping.
|
|
215
|
+
* Plugins, core, enabled, and modules remain intact so the manager can be reused.
|
|
216
|
+
*/
|
|
217
|
+
shutdown(): Effect.Effect<boolean, Error>;
|
|
77
218
|
}
|
|
78
219
|
|
|
79
220
|
/**
|
|
@@ -91,6 +232,7 @@ class ManagerImpl implements PluginManager {
|
|
|
91
232
|
readonly activation = Effect.runSync(PubSub.unbounded<ActivationMessage>());
|
|
92
233
|
readonly capabilities: CapabilityManager.CapabilityManager;
|
|
93
234
|
readonly registry: Registry.Registry;
|
|
235
|
+
readonly pluginRegistry: PluginRegistry.Manager;
|
|
94
236
|
|
|
95
237
|
private readonly _pluginsAtom: Atom.Writable<Plugin.Plugin[]>;
|
|
96
238
|
private readonly _coreAtom: Atom.Writable<string[]>;
|
|
@@ -99,12 +241,41 @@ class ManagerImpl implements PluginManager {
|
|
|
99
241
|
private readonly _activeAtom: Atom.Writable<string[]>;
|
|
100
242
|
private readonly _eventsFiredAtom: Atom.Writable<string[]>;
|
|
101
243
|
private readonly _pendingResetAtom: Atom.Writable<string[]>;
|
|
244
|
+
private readonly _failedAtom: Atom.Writable<PluginFailure[]>;
|
|
102
245
|
private readonly _pluginLoader: ManagerOptions['pluginLoader'];
|
|
246
|
+
private readonly _onRemove: ManagerOptions['onRemove'];
|
|
247
|
+
private readonly _loadTimeout: Duration.DurationInput;
|
|
248
|
+
private readonly _activationTimeout: Duration.DurationInput;
|
|
103
249
|
private readonly _capabilities = new Map<string, Capability.Any[]>();
|
|
104
250
|
private readonly _moduleMemoMap = new Map<Plugin.PluginModule['id'], Deferred.Deferred<Capability.Any[], Error>>();
|
|
105
251
|
private readonly _moduleSemaphores = new Map<Plugin.PluginModule['id'], Effect.Semaphore>();
|
|
252
|
+
// Coalesces concurrent `_resolveLazyPlugin` calls per plugin id. Without
|
|
253
|
+
// this, two callers entering `enable(id)` before the swap completes would
|
|
254
|
+
// each invoke `mod.default(options)` and produce distinct module objects,
|
|
255
|
+
// defeating `_addModule`'s reference-equality dedupe and racing the
|
|
256
|
+
// `_pluginsAtom` swap.
|
|
257
|
+
private readonly _resolvingPlugins = new Map<string, Deferred.Deferred<Plugin.Plugin, Plugin.LazyPluginError>>();
|
|
258
|
+
// Tracks dev-source plugins (loaded via a Vite dev server) keyed by id.
|
|
259
|
+
// When `shadow` is present, the entry has displaced an existing plugin —
|
|
260
|
+
// `remove` reinstates it and re-enables iff `wasEnabled`. Entries without a
|
|
261
|
+
// shadow are dev plugins with no underlying registry/builtin to restore.
|
|
262
|
+
// The atom mirrors the map's keys for UI subscribers (they don't need the
|
|
263
|
+
// shadow internals); the two stay in sync via {@link _markDev}/{@link _unmarkDev}.
|
|
264
|
+
private readonly _devPlugins = new Map<string, { shadow?: { plugin: Plugin.Plugin; wasEnabled: boolean } }>();
|
|
265
|
+
private readonly _devPluginIdsAtom: Atom.Writable<string[]>;
|
|
106
266
|
private readonly _activatingEvents = Effect.runSync(Ref.make<string[]>([]));
|
|
107
267
|
private readonly _activatingModules = Effect.runSync(Ref.make<string[]>([]));
|
|
268
|
+
private readonly _inFlightFibers = Effect.runSync(Ref.make<Array<Fiber.Fiber<unknown, unknown>>>([]));
|
|
269
|
+
private readonly _shutdownSemaphore = Effect.runSync(Effect.makeSemaphore(1));
|
|
270
|
+
private readonly _shuttingDown = Effect.runSync(Ref.make(false));
|
|
271
|
+
// Tracks the constructor-launched core/enabled `enable()` calls so that
|
|
272
|
+
// `activate` can wait for module registration before dispatching events.
|
|
273
|
+
// Lazy plugins make `enable` asynchronous (a dynamic `import()` happens
|
|
274
|
+
// inside it), so without this synchronization an `activate` triggered
|
|
275
|
+
// immediately after `make` could fire on an empty module set. Failures
|
|
276
|
+
// are wrapped in `PluginInitializationError` so awaiters get a tagged
|
|
277
|
+
// error rather than the wide `Error` produced by the underlying chain.
|
|
278
|
+
private readonly _initialization = Effect.runSync(Deferred.make<void, PluginInitializationError>());
|
|
108
279
|
|
|
109
280
|
constructor({
|
|
110
281
|
pluginLoader,
|
|
@@ -112,13 +283,21 @@ class ManagerImpl implements PluginManager {
|
|
|
112
283
|
core = plugins.map(({ meta }) => meta.id),
|
|
113
284
|
enabled = [],
|
|
114
285
|
registry,
|
|
286
|
+
pluginRegistryProvider,
|
|
287
|
+
onRemove,
|
|
288
|
+
loadTimeout = DEFAULT_LOAD_TIMEOUT,
|
|
289
|
+
activationTimeout = DEFAULT_ACTIVATION_TIMEOUT,
|
|
115
290
|
}: ManagerOptions) {
|
|
116
291
|
this.registry = registry ?? Registry.make();
|
|
117
292
|
this.capabilities = CapabilityManager.make({
|
|
118
293
|
registry: this.registry,
|
|
119
294
|
});
|
|
295
|
+
this.pluginRegistry = new PluginRegistry.Manager(pluginRegistryProvider, this.registry);
|
|
120
296
|
|
|
121
297
|
this._pluginLoader = pluginLoader;
|
|
298
|
+
this._onRemove = onRemove;
|
|
299
|
+
this._loadTimeout = loadTimeout;
|
|
300
|
+
this._activationTimeout = activationTimeout;
|
|
122
301
|
this._pluginsAtom = Atom.make(plugins).pipe(Atom.keepAlive);
|
|
123
302
|
this._coreAtom = Atom.make(core).pipe(Atom.keepAlive);
|
|
124
303
|
this._enabledAtom = Atom.make(enabled).pipe(Atom.keepAlive);
|
|
@@ -126,8 +305,22 @@ class ManagerImpl implements PluginManager {
|
|
|
126
305
|
this._activeAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
|
|
127
306
|
this._eventsFiredAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
|
|
128
307
|
this._pendingResetAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
|
|
308
|
+
this._failedAtom = Atom.make<PluginFailure[]>([]).pipe(Atom.keepAlive);
|
|
309
|
+
this._devPluginIdsAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
|
|
129
310
|
plugins.forEach((plugin) => this._addPlugin(plugin));
|
|
130
|
-
|
|
311
|
+
// Dedupe before mapping to `enable` — `core` and `enabled` may overlap (an
|
|
312
|
+
// app-supplied plugin can be in both), and concurrent `enable(id)` calls
|
|
313
|
+
// for the same id are not idempotent (each would re-run the lazy resolve
|
|
314
|
+
// and double-register modules). `new Set([...])` preserves first-seen
|
|
315
|
+
// order which matches the natural core-before-enabled precedence.
|
|
316
|
+
const initialIds = [...new Set([...core, ...enabled])];
|
|
317
|
+
void Effect.all(initialIds.map((id) => this.enable(id)))
|
|
318
|
+
.pipe(
|
|
319
|
+
Effect.mapError((cause) => new PluginInitializationError({ cause })),
|
|
320
|
+
Effect.tap(() => Deferred.succeed(this._initialization, undefined)),
|
|
321
|
+
Effect.tapErrorCause((cause) => Deferred.failCause(this._initialization, cause)),
|
|
322
|
+
)
|
|
323
|
+
.pipe(runAndForwardErrors);
|
|
131
324
|
}
|
|
132
325
|
|
|
133
326
|
get plugins(): Atom.Atom<readonly Plugin.Plugin[]> {
|
|
@@ -173,6 +366,20 @@ class ManagerImpl implements PluginManager {
|
|
|
173
366
|
return this._pendingResetAtom;
|
|
174
367
|
}
|
|
175
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Plugins that failed to load or activate.
|
|
371
|
+
*/
|
|
372
|
+
get failed(): Atom.Atom<readonly PluginFailure[]> {
|
|
373
|
+
return this._failedAtom;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Ids of currently-registered plugins that came from a dev source.
|
|
378
|
+
*/
|
|
379
|
+
get devPluginIds(): Atom.Atom<readonly string[]> {
|
|
380
|
+
return this._devPluginIdsAtom;
|
|
381
|
+
}
|
|
382
|
+
|
|
176
383
|
getPlugins(): readonly Plugin.Plugin[] {
|
|
177
384
|
return this._get(this._pluginsAtom);
|
|
178
385
|
}
|
|
@@ -201,16 +408,82 @@ class ManagerImpl implements PluginManager {
|
|
|
201
408
|
return this._get(this._pendingResetAtom);
|
|
202
409
|
}
|
|
203
410
|
|
|
411
|
+
getFailed(): readonly PluginFailure[] {
|
|
412
|
+
return this._get(this._failedAtom);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
getDevPluginIds(): readonly string[] {
|
|
416
|
+
return this._get(this._devPluginIdsAtom);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Marks `id` as dev-sourced. If the plugin displaced an existing one, pass
|
|
421
|
+
* the shadow snapshot so `remove` can restore it. Repeat calls (e.g. a dev
|
|
422
|
+
* plugin reload) preserve the original shadow target — restoration always
|
|
423
|
+
* unwinds back to the real underlying plugin, never an intermediate dev build.
|
|
424
|
+
*/
|
|
425
|
+
private _markDev(id: string, shadow?: { plugin: Plugin.Plugin; wasEnabled: boolean }): void {
|
|
426
|
+
if (this._devPlugins.has(id)) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this._devPlugins.set(id, { shadow });
|
|
430
|
+
this._update(this._devPluginIdsAtom, (ids) => (ids.includes(id) ? ids : [...ids, id]));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Drops the dev-plugin entry and returns its shadow data (if any) for restoration. */
|
|
434
|
+
private _unmarkDev(id: string): { plugin: Plugin.Plugin; wasEnabled: boolean } | undefined {
|
|
435
|
+
const entry = this._devPlugins.get(id);
|
|
436
|
+
this._devPlugins.delete(id);
|
|
437
|
+
this._update(this._devPluginIdsAtom, (ids) => ids.filter((existing) => existing !== id));
|
|
438
|
+
return entry?.shadow;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
clearFailure(id: string): boolean {
|
|
442
|
+
const current = this._get(this._failedAtom);
|
|
443
|
+
if (!current.some((failure) => failure.id === id)) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
this._set(
|
|
447
|
+
this._failedAtom,
|
|
448
|
+
current.filter((failure) => failure.id !== id),
|
|
449
|
+
);
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
204
453
|
/**
|
|
205
454
|
* Adds a plugin to the manager via the plugin loader.
|
|
455
|
+
* The plugin is registered but not enabled; call `enable` separately to activate it.
|
|
206
456
|
* @param id The id of the plugin.
|
|
207
457
|
*/
|
|
208
|
-
add(id: string): Effect.Effect<
|
|
458
|
+
add(id: string): Effect.Effect<Plugin.Plugin, Error> {
|
|
209
459
|
return Effect.gen(this, function* () {
|
|
210
460
|
log('add plugin', { id });
|
|
211
|
-
const plugin = yield* this._pluginLoader(id);
|
|
212
|
-
|
|
213
|
-
|
|
461
|
+
const { plugin, dev = false } = yield* this._pluginLoader(id);
|
|
462
|
+
const pluginId = plugin.meta.id;
|
|
463
|
+
const existing = this._getPlugin(pluginId);
|
|
464
|
+
|
|
465
|
+
if (dev && existing && existing !== plugin) {
|
|
466
|
+
// Shadow path: a plugin with this id is already registered (a builtin,
|
|
467
|
+
// a registry install, or a previous dev load). Disable it, stash it,
|
|
468
|
+
// and swap the dev plugin into the same id slot. The dialog will call
|
|
469
|
+
// `enable(pluginId)` next, which activates the dev plugin's modules.
|
|
470
|
+
// `_markDev` is a no-op when the id is already tracked, so a dev-plugin
|
|
471
|
+
// reload (after editing source) keeps the *original* shadow target —
|
|
472
|
+
// removal restores the real underlying plugin, not an intermediate build.
|
|
473
|
+
const wasEnabled = this._get(this._enabledAtom).includes(pluginId);
|
|
474
|
+
if (wasEnabled) {
|
|
475
|
+
yield* this.disable(pluginId);
|
|
476
|
+
}
|
|
477
|
+
this._markDev(pluginId, { plugin: existing, wasEnabled });
|
|
478
|
+
this._update(this._pluginsAtom, (plugins) => plugins.map((p) => (p.meta.id === pluginId ? plugin : p)));
|
|
479
|
+
} else {
|
|
480
|
+
this._addPlugin(plugin);
|
|
481
|
+
if (dev) {
|
|
482
|
+
this._markDev(pluginId);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return plugin;
|
|
214
487
|
});
|
|
215
488
|
}
|
|
216
489
|
|
|
@@ -221,11 +494,17 @@ class ManagerImpl implements PluginManager {
|
|
|
221
494
|
enable(id: string): Effect.Effect<boolean, Error> {
|
|
222
495
|
return Effect.gen(this, function* () {
|
|
223
496
|
log('enable plugin', { id });
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
497
|
+
const stub = this._getPlugin(id);
|
|
498
|
+
if (!stub) {
|
|
226
499
|
return false;
|
|
227
500
|
}
|
|
228
501
|
|
|
502
|
+
// Clear any prior failure record so a retry starts from a clean slate.
|
|
503
|
+
// The failure stays on the atom only if this attempt also fails.
|
|
504
|
+
this.clearFailure(id);
|
|
505
|
+
|
|
506
|
+
const plugin = yield* this._resolveLazyPlugin(stub);
|
|
507
|
+
|
|
229
508
|
this._update(this._enabledAtom, (enabled) => (enabled.includes(id) ? enabled : [...enabled, id]));
|
|
230
509
|
|
|
231
510
|
plugin.modules.forEach((module) => {
|
|
@@ -243,19 +522,108 @@ class ManagerImpl implements PluginManager {
|
|
|
243
522
|
});
|
|
244
523
|
}
|
|
245
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Resolves a lazy plugin stub (returned by {@link Plugin.lazy}) to its
|
|
527
|
+
* loaded form and swaps it into `_pluginsAtom`. Returns the input unchanged
|
|
528
|
+
* when the plugin is already resolved, so callers can `yield*` this
|
|
529
|
+
* unconditionally. The lazy stub carries `meta` synchronously but its
|
|
530
|
+
* `modules` list is empty until the loader resolves; the swap ensures
|
|
531
|
+
* subsequent enable/disable operations see the resolved plugin.
|
|
532
|
+
*
|
|
533
|
+
* Concurrent calls for the same id are coalesced via `_resolvingPlugins`:
|
|
534
|
+
* the first caller starts the resolution, every subsequent caller awaits
|
|
535
|
+
* the same `Deferred`. On failure we publish a `lazy:<id>` error message
|
|
536
|
+
* and skip the atom swap so the failure is observable to the activation
|
|
537
|
+
* subscriber and a retry can be attempted.
|
|
538
|
+
*/
|
|
539
|
+
private _resolveLazyPlugin(plugin: Plugin.Plugin): Effect.Effect<Plugin.Plugin, Plugin.LazyPluginError> {
|
|
540
|
+
return Effect.gen(this, function* () {
|
|
541
|
+
if (!Plugin.isLazy(plugin)) {
|
|
542
|
+
return plugin;
|
|
543
|
+
}
|
|
544
|
+
const id = plugin.meta.id;
|
|
545
|
+
|
|
546
|
+
const existing = this._resolvingPlugins.get(id);
|
|
547
|
+
if (existing) {
|
|
548
|
+
return yield* Deferred.await(existing);
|
|
549
|
+
}
|
|
550
|
+
const deferred = yield* Deferred.make<Plugin.Plugin, Plugin.LazyPluginError>();
|
|
551
|
+
this._resolvingPlugins.set(id, deferred);
|
|
552
|
+
|
|
553
|
+
return yield* Effect.gen(this, function* () {
|
|
554
|
+
log('resolving lazy plugin', { id });
|
|
555
|
+
yield* PubSub.publish(this.activation, { event: '', state: 'activating', module: `lazy:${id}` });
|
|
556
|
+
const resolvedPlugin = yield* Plugin.resolveLazy(plugin).pipe(
|
|
557
|
+
// Cap how long a remote import can hang. Without this the host can
|
|
558
|
+
// sit on a pending dynamic `import()` indefinitely if the plugin's
|
|
559
|
+
// server is unreachable, which stalls every caller awaiting
|
|
560
|
+
// `enable(id)` and (transitively) the manager's initialization.
|
|
561
|
+
Effect.timeoutFail({
|
|
562
|
+
duration: this._loadTimeout,
|
|
563
|
+
onTimeout: () =>
|
|
564
|
+
new Plugin.LazyPluginError({
|
|
565
|
+
context: { id, reason: 'load-failed' },
|
|
566
|
+
cause: new PluginTimeoutError({ context: { id, phase: 'load' as PluginFailurePhase } }),
|
|
567
|
+
}),
|
|
568
|
+
}),
|
|
569
|
+
);
|
|
570
|
+
this._update(this._pluginsAtom, (plugins) => plugins.map((p) => (p.meta.id === id ? resolvedPlugin : p)));
|
|
571
|
+
yield* PubSub.publish(this.activation, { event: '', state: 'activated', module: `lazy:${id}` });
|
|
572
|
+
return resolvedPlugin;
|
|
573
|
+
}).pipe(
|
|
574
|
+
Effect.tapError((error) =>
|
|
575
|
+
Effect.gen(this, function* () {
|
|
576
|
+
yield* PubSub.publish(this.activation, { event: '', state: 'error', module: `lazy:${id}`, error });
|
|
577
|
+
this._recordFailure(id, 'load', error);
|
|
578
|
+
this._scheduleAutoDisable(id);
|
|
579
|
+
}),
|
|
580
|
+
),
|
|
581
|
+
Effect.tap((value) => Deferred.succeed(deferred, value)),
|
|
582
|
+
Effect.tapErrorCause((cause) => Deferred.failCause(deferred, cause)),
|
|
583
|
+
Effect.ensuring(Effect.sync(() => this._resolvingPlugins.delete(id))),
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
246
588
|
/**
|
|
247
589
|
* Removes a plugin from the manager.
|
|
248
590
|
* @param id The id of the plugin.
|
|
249
591
|
*/
|
|
250
|
-
remove(id: string): boolean {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
592
|
+
remove(id: string): Effect.Effect<boolean, Error> {
|
|
593
|
+
return Effect.gen(this, function* () {
|
|
594
|
+
log('remove plugin', { id });
|
|
595
|
+
const wasDev = this._devPlugins.has(id);
|
|
596
|
+
const disabled = yield* this.disable(id);
|
|
597
|
+
if (!disabled) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
256
600
|
|
|
257
|
-
|
|
258
|
-
|
|
601
|
+
this._removePlugin(id);
|
|
602
|
+
if (this._onRemove) {
|
|
603
|
+
this._runForkedFiber(
|
|
604
|
+
this._onRemove(id).pipe(
|
|
605
|
+
Effect.tapError((error) => Effect.sync(() => log.warn('plugin remove hook failed', { id, error }))),
|
|
606
|
+
Effect.ignore,
|
|
607
|
+
),
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// If a dev plugin was shadowing an existing plugin, reinstate the
|
|
612
|
+
// original now that the dev plugin is gone. Re-enable only if the
|
|
613
|
+
// original was enabled at shadow time — preserving the user's intent
|
|
614
|
+
// for plugins they had explicitly disabled before iterating on a dev
|
|
615
|
+
// build.
|
|
616
|
+
if (wasDev) {
|
|
617
|
+
const shadow = this._unmarkDev(id);
|
|
618
|
+
if (shadow) {
|
|
619
|
+
this._addPlugin(shadow.plugin);
|
|
620
|
+
if (shadow.wasEnabled) {
|
|
621
|
+
yield* this.enable(id);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return true;
|
|
626
|
+
});
|
|
259
627
|
}
|
|
260
628
|
|
|
261
629
|
/**
|
|
@@ -299,146 +667,36 @@ class ManagerImpl implements PluginManager {
|
|
|
299
667
|
): Effect.Effect<boolean, Error> {
|
|
300
668
|
const key = typeof event === 'string' ? event : ActivationEvent.eventKey(event);
|
|
301
669
|
return Effect.gen(this, function* () {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const pendingIndex = this._get(this._pendingResetAtom).findIndex((event) => event === key);
|
|
305
|
-
if (pendingIndex !== -1) {
|
|
306
|
-
this._update(this._pendingResetAtom, (pending) => pending.filter((event) => event !== key));
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const activatingEvents = yield* this._activatingEvents;
|
|
310
|
-
const activatingModules = yield* this._activatingModules;
|
|
311
|
-
const modules = this._getInactiveModulesByEvent(key).filter((module) => {
|
|
312
|
-
const allOf = ActivationEvent.isAllOf(module.activatesOn);
|
|
313
|
-
if (!allOf) {
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Check to see if all of the events in the `allOf` have been fired.
|
|
318
|
-
// An event can be considered "fired" if it is in the `eventsFired` list or if it is currently being activated.
|
|
319
|
-
const events = ActivationEvent.getEvents(module.activatesOn).filter(
|
|
320
|
-
(event) => ActivationEvent.eventKey(event) !== key,
|
|
321
|
-
);
|
|
322
|
-
return (
|
|
323
|
-
events.every(
|
|
324
|
-
(event) =>
|
|
325
|
-
this._get(this._eventsFiredAtom).includes(ActivationEvent.eventKey(event)) ||
|
|
326
|
-
activatingEvents.includes(ActivationEvent.eventKey(event)),
|
|
327
|
-
) && !activatingModules.includes(module.id)
|
|
328
|
-
);
|
|
329
|
-
});
|
|
330
|
-
yield* Ref.update(this._activatingModules, (activating) =>
|
|
331
|
-
Array.appendAll(
|
|
332
|
-
activating,
|
|
333
|
-
modules.map((module) => module.id),
|
|
334
|
-
),
|
|
335
|
-
);
|
|
336
|
-
if (modules.length === 0) {
|
|
337
|
-
log('no modules to activate', { key });
|
|
338
|
-
if (!this._get(this._eventsFiredAtom).includes(key)) {
|
|
339
|
-
this._update(this._eventsFiredAtom, (events) => [...events, key]);
|
|
340
|
-
}
|
|
670
|
+
if (yield* this._isShuttingDown()) {
|
|
671
|
+
log('skipping activation during shutdown', { key, ...params });
|
|
341
672
|
return false;
|
|
342
673
|
}
|
|
343
674
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
modules,
|
|
350
|
-
Array.flatMap((module) => module.activatesBefore ?? []),
|
|
351
|
-
HashSet.fromIterable,
|
|
352
|
-
HashSet.toValues,
|
|
353
|
-
Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
|
|
354
|
-
);
|
|
355
|
-
yield* Function.pipe(
|
|
356
|
-
beforeEvents,
|
|
357
|
-
Array.map((event) => this.activate(event, { before: key })),
|
|
358
|
-
Effect.allWith({ concurrency: 'unbounded' }),
|
|
359
|
-
together(
|
|
360
|
-
Effect.sleep(Duration.seconds(10)).pipe(
|
|
361
|
-
Effect.andThen(
|
|
362
|
-
Effect.sync(() =>
|
|
363
|
-
log.warn('activatesBefore is taking a long time', {
|
|
364
|
-
event: key,
|
|
365
|
-
beforeEvents: beforeEvents.map(ActivationEvent.eventKey),
|
|
366
|
-
}),
|
|
367
|
-
),
|
|
368
|
-
),
|
|
369
|
-
),
|
|
370
|
-
),
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
// Concurrently triggers loading of lazy capabilities.
|
|
374
|
-
const getCapabilities = yield* Function.pipe(
|
|
375
|
-
modules,
|
|
376
|
-
Array.map((mod) => this._loadModule(mod)),
|
|
377
|
-
Effect.allWith({ concurrency: 'unbounded' }),
|
|
378
|
-
Effect.catchAll((error) => {
|
|
379
|
-
return Effect.gen(this, function* () {
|
|
380
|
-
yield* PubSub.publish(this.activation, { event: key, state: 'error', error });
|
|
381
|
-
return yield* Effect.fail(error);
|
|
382
|
-
});
|
|
383
|
-
}),
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
// Contribute the capabilities from the activated modules.
|
|
387
|
-
yield* Function.pipe(
|
|
388
|
-
modules,
|
|
389
|
-
Array.zip(getCapabilities),
|
|
390
|
-
Array.map(([module, capabilities]) => this._contributeCapabilities(module, capabilities)),
|
|
391
|
-
// TODO(wittjosiah): This currently can't be run in parallel.
|
|
392
|
-
// Running this with concurrency causes races with `allOf` activation events.
|
|
393
|
-
Effect.all,
|
|
394
|
-
);
|
|
675
|
+
// Wait for the constructor's core/enabled `enable()` chain — including
|
|
676
|
+
// any async dynamic imports for lazy plugins — to finish registering
|
|
677
|
+
// modules. Without this, dispatching to an empty module set is the
|
|
678
|
+
// observable symptom of the race.
|
|
679
|
+
yield* Deferred.await(this._initialization);
|
|
395
680
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
HashSet.toValues,
|
|
402
|
-
Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
|
|
403
|
-
);
|
|
404
|
-
yield* Function.pipe(
|
|
405
|
-
afterEvents,
|
|
406
|
-
Array.map((event) => this.activate(event, { after: key })),
|
|
407
|
-
Effect.allWith({ concurrency: 'unbounded' }),
|
|
408
|
-
together(
|
|
409
|
-
Effect.sleep(Duration.seconds(10)).pipe(
|
|
410
|
-
Effect.andThen(
|
|
411
|
-
Effect.sync(() =>
|
|
412
|
-
log.warn('activatesAfter is taking a long time', {
|
|
413
|
-
event: key,
|
|
414
|
-
afterEvents: afterEvents.map(ActivationEvent.eventKey),
|
|
415
|
-
}),
|
|
416
|
-
),
|
|
681
|
+
return yield* Effect.withFiberRuntime<boolean, Error>((fiber) =>
|
|
682
|
+
this._activateEvent(key, params, fiber).pipe(
|
|
683
|
+
together(
|
|
684
|
+
Effect.sleep(Duration.seconds(15)).pipe(
|
|
685
|
+
Effect.andThen(Effect.sync(() => log.warn('event activation is taking a long time', { event: key }))),
|
|
417
686
|
),
|
|
418
687
|
),
|
|
688
|
+
Performance.addTrackEntry({
|
|
689
|
+
name: typeof event === 'string' ? event : ActivationEvent.eventKey(event),
|
|
690
|
+
devtools: {
|
|
691
|
+
dataType: 'track-entry',
|
|
692
|
+
track: 'Event Activation',
|
|
693
|
+
trackGroup: 'Composer',
|
|
694
|
+
color: 'primary',
|
|
695
|
+
},
|
|
696
|
+
}),
|
|
419
697
|
),
|
|
420
698
|
);
|
|
421
|
-
|
|
422
|
-
yield* Ref.update(this._activatingEvents, (activating) => Array.filter(activating, (event) => event !== key));
|
|
423
|
-
yield* Ref.update(this._activatingModules, (activating) =>
|
|
424
|
-
Array.filter(activating, (module) => !modules.map((module) => module.id).includes(module)),
|
|
425
|
-
);
|
|
426
|
-
|
|
427
|
-
if (!this._get(this._eventsFiredAtom).includes(key)) {
|
|
428
|
-
this._update(this._eventsFiredAtom, (events) => [...events, key]);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
yield* PubSub.publish(this.activation, { event: key, state: 'activated' });
|
|
432
|
-
log('activated', { key });
|
|
433
|
-
|
|
434
|
-
return true;
|
|
435
|
-
}).pipe(
|
|
436
|
-
together(
|
|
437
|
-
Effect.sleep(Duration.seconds(15)).pipe(
|
|
438
|
-
Effect.andThen(Effect.sync(() => log.warn('event activation is taking a long time', { event: key }))),
|
|
439
|
-
),
|
|
440
|
-
),
|
|
441
|
-
);
|
|
699
|
+
});
|
|
442
700
|
}
|
|
443
701
|
|
|
444
702
|
/**
|
|
@@ -485,6 +743,40 @@ class ManagerImpl implements PluginManager {
|
|
|
485
743
|
});
|
|
486
744
|
}
|
|
487
745
|
|
|
746
|
+
shutdown(): Effect.Effect<boolean, Error> {
|
|
747
|
+
return this._shutdownSemaphore.withPermits(1)(
|
|
748
|
+
Effect.gen(this, function* () {
|
|
749
|
+
yield* Ref.set(this._shuttingDown, true);
|
|
750
|
+
log('shutdown');
|
|
751
|
+
|
|
752
|
+
yield* this._interruptInFlightActivations();
|
|
753
|
+
|
|
754
|
+
const activeIds = [...this._get(this._activeAtom)].reverse();
|
|
755
|
+
const allModules = this._get(this._modulesAtom);
|
|
756
|
+
const modulesToDeactivate = activeIds
|
|
757
|
+
.map((id) => allModules.find((module) => module.id === id))
|
|
758
|
+
.filter((module): module is Plugin.PluginModule => module != null);
|
|
759
|
+
|
|
760
|
+
for (const module of modulesToDeactivate) {
|
|
761
|
+
yield* this._deactivateModule(module);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
this._set(this._eventsFiredAtom, []);
|
|
765
|
+
this._set(this._pendingResetAtom, []);
|
|
766
|
+
this._moduleMemoMap.clear();
|
|
767
|
+
yield* Ref.set(this._activatingEvents, []);
|
|
768
|
+
yield* Ref.set(this._activatingModules, []);
|
|
769
|
+
|
|
770
|
+
log('shutdown complete');
|
|
771
|
+
return true;
|
|
772
|
+
}).pipe(Effect.ensuring(Ref.set(this._shuttingDown, false))),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
//
|
|
777
|
+
// State helpers
|
|
778
|
+
//
|
|
779
|
+
|
|
488
780
|
private _get<T>(atom: Atom.Atom<T>): T {
|
|
489
781
|
return this.registry.get(atom);
|
|
490
782
|
}
|
|
@@ -497,30 +789,52 @@ class ManagerImpl implements PluginManager {
|
|
|
497
789
|
this._set(atom, updater(this._get(atom)));
|
|
498
790
|
}
|
|
499
791
|
|
|
500
|
-
private
|
|
501
|
-
|
|
502
|
-
// TODO(wittjosiah): Find a way to add a warning for duplicate plugins that doesn't cause log spam.
|
|
503
|
-
this._update(this._pluginsAtom, (plugins) => (plugins.includes(plugin) ? plugins : [...plugins, plugin]));
|
|
792
|
+
private _isShuttingDown(): Effect.Effect<boolean> {
|
|
793
|
+
return Ref.get(this._shuttingDown);
|
|
504
794
|
}
|
|
505
795
|
|
|
506
|
-
private
|
|
507
|
-
|
|
508
|
-
this._update(this._pluginsAtom, (plugins) => plugins.filter((plugin) => plugin.meta.id !== id));
|
|
796
|
+
private _getPlugin(id: string): Plugin.Plugin | undefined {
|
|
797
|
+
return this._get(this._pluginsAtom).find((plugin) => plugin.meta.id === id);
|
|
509
798
|
}
|
|
510
799
|
|
|
511
|
-
private
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
this._update(this._modulesAtom, (modules) => (modules.includes(module) ? modules : [...modules, module]));
|
|
800
|
+
private _getPluginIdForModule(moduleId: string): string | undefined {
|
|
801
|
+
return this._get(this._pluginsAtom).find((plugin) => plugin.modules.some((module) => module.id === moduleId))?.meta
|
|
802
|
+
.id;
|
|
515
803
|
}
|
|
516
804
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
805
|
+
/**
|
|
806
|
+
* Records a failure for a plugin. Latest failure wins so the registry UI
|
|
807
|
+
* always sees the most recent reason. Walks the `cause` chain when checking
|
|
808
|
+
* for timeouts: lazy-load timeouts arrive wrapped in `LazyPluginError` (the
|
|
809
|
+
* timeout is the cause), but the operator-visible reason should still be
|
|
810
|
+
* `'timeout'`.
|
|
811
|
+
*/
|
|
812
|
+
private _recordFailure(id: string, phase: PluginFailurePhase, error: Error): void {
|
|
813
|
+
const reason: PluginFailureReason = isTimeoutCause(error) ? 'timeout' : 'error';
|
|
814
|
+
const failure: PluginFailure = { id, phase, reason, error, timestamp: Date.now() };
|
|
815
|
+
log.warn('plugin failed', { id, phase, reason, error: error.message });
|
|
816
|
+
this._update(this._failedAtom, (current) => [...current.filter((entry) => entry.id !== id), failure]);
|
|
520
817
|
}
|
|
521
818
|
|
|
522
|
-
|
|
523
|
-
|
|
819
|
+
/**
|
|
820
|
+
* Fire-and-forget disable of a failed plugin. Forked because a failure can
|
|
821
|
+
* happen mid-activation chain — yielding a `disable` inline would deadlock
|
|
822
|
+
* on the shared semaphores. Core plugins are skipped (the host opted into
|
|
823
|
+
* them being non-removable; the failure record is enough signal).
|
|
824
|
+
*/
|
|
825
|
+
private _scheduleAutoDisable(id: string): void {
|
|
826
|
+
if (this._get(this._coreAtom).includes(id)) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (!this._get(this._enabledAtom).includes(id)) {
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
this._runForkedFiber(
|
|
833
|
+
this.disable(id).pipe(
|
|
834
|
+
Effect.tapError((error) => Effect.sync(() => log.warn('auto-disable failed', { id, error }))),
|
|
835
|
+
Effect.ignore,
|
|
836
|
+
),
|
|
837
|
+
);
|
|
524
838
|
}
|
|
525
839
|
|
|
526
840
|
private _getActiveModules(): Plugin.PluginModule[] {
|
|
@@ -560,6 +874,273 @@ class ManagerImpl implements PluginManager {
|
|
|
560
874
|
}
|
|
561
875
|
}
|
|
562
876
|
|
|
877
|
+
private _clearPendingReset(key: string): void {
|
|
878
|
+
const pendingIndex = this._get(this._pendingResetAtom).findIndex((event) => event === key);
|
|
879
|
+
if (pendingIndex !== -1) {
|
|
880
|
+
this._update(this._pendingResetAtom, (pending) => pending.filter((event) => event !== key));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
//
|
|
885
|
+
// Fiber helpers
|
|
886
|
+
//
|
|
887
|
+
|
|
888
|
+
private _interruptInFlightActivations(): Effect.Effect<void> {
|
|
889
|
+
return Effect.gen(this, function* () {
|
|
890
|
+
const inFlightFibers = yield* Ref.get(this._inFlightFibers);
|
|
891
|
+
yield* Effect.forEach(inFlightFibers, (fiber) => Fiber.interrupt(fiber), {
|
|
892
|
+
concurrency: 'unbounded',
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private _trackFiber(
|
|
898
|
+
ref: Ref.Ref<Array<Fiber.Fiber<unknown, unknown>>>,
|
|
899
|
+
fiber: Fiber.Fiber<unknown, unknown>,
|
|
900
|
+
): Effect.Effect<void> {
|
|
901
|
+
return Ref.update(ref, (fibers) => [...fibers, fiber]);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
private _untrackFiber(
|
|
905
|
+
ref: Ref.Ref<Array<Fiber.Fiber<unknown, unknown>>>,
|
|
906
|
+
fiber: Fiber.Fiber<unknown, unknown>,
|
|
907
|
+
): Effect.Effect<void> {
|
|
908
|
+
return Ref.update(ref, (fibers) => fibers.filter((trackedFiber) => trackedFiber !== fiber));
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Spawns an effect on the default runtime and registers the resulting fiber in
|
|
913
|
+
* `_inFlightFibers` so {@link shutdown} can interrupt it. Used from sync entry
|
|
914
|
+
* points like {@link remove} where there is no enclosing Effect to fork from;
|
|
915
|
+
* inside an Effect chain prefer the existing track/await/untrack pattern.
|
|
916
|
+
*/
|
|
917
|
+
private _runForkedFiber<E>(effect: Effect.Effect<void, E>): void {
|
|
918
|
+
const fiber = Effect.runFork(effect);
|
|
919
|
+
Effect.runSync(this._trackFiber(this._inFlightFibers, fiber));
|
|
920
|
+
Effect.runFork(Fiber.await(fiber).pipe(Effect.andThen(() => this._untrackFiber(this._inFlightFibers, fiber))));
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
//
|
|
924
|
+
// Registration helpers
|
|
925
|
+
//
|
|
926
|
+
|
|
927
|
+
private _addPlugin(plugin: Plugin.Plugin): void {
|
|
928
|
+
log('add plugin', { id: plugin.meta.id });
|
|
929
|
+
// TODO(wittjosiah): Find a way to add a warning for duplicate plugins that doesn't cause log spam.
|
|
930
|
+
this._update(this._pluginsAtom, (plugins) => (plugins.includes(plugin) ? plugins : [...plugins, plugin]));
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private _removePlugin(id: string): void {
|
|
934
|
+
log('remove plugin', { id });
|
|
935
|
+
this._update(this._pluginsAtom, (plugins) => plugins.filter((plugin) => plugin.meta.id !== id));
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
private _addModule(module: Plugin.PluginModule): void {
|
|
939
|
+
log('add module', { id: module.id });
|
|
940
|
+
// TODO(wittjosiah): Find a way to add a warning for duplicate modules that doesn't cause log spam.
|
|
941
|
+
this._update(this._modulesAtom, (modules) => (modules.includes(module) ? modules : [...modules, module]));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
private _removeModule(id: string): void {
|
|
945
|
+
log('remove module', { id });
|
|
946
|
+
this._update(this._modulesAtom, (modules) => modules.filter((module) => module.id !== id));
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
//
|
|
950
|
+
// Activation helpers
|
|
951
|
+
//
|
|
952
|
+
|
|
953
|
+
private _activateEvent(
|
|
954
|
+
key: string,
|
|
955
|
+
params: { before?: string; after?: string } | undefined,
|
|
956
|
+
fiber: Fiber.Fiber<unknown, unknown>,
|
|
957
|
+
): Effect.Effect<boolean, Error> {
|
|
958
|
+
return Effect.gen(this, function* () {
|
|
959
|
+
yield* this._trackFiber(this._inFlightFibers, fiber);
|
|
960
|
+
log('activating', { key, ...params });
|
|
961
|
+
yield* Ref.update(this._activatingEvents, (activating) => Array.append(activating, key));
|
|
962
|
+
this._clearPendingReset(key);
|
|
963
|
+
|
|
964
|
+
const activatingEvents = yield* this._activatingEvents;
|
|
965
|
+
const activatingModules = yield* this._activatingModules;
|
|
966
|
+
const modules = this._getModulesForActivation(key, activatingEvents, activatingModules);
|
|
967
|
+
if (modules.length === 0) {
|
|
968
|
+
log('no modules to activate', { key });
|
|
969
|
+
if (!this._get(this._eventsFiredAtom).includes(key)) {
|
|
970
|
+
this._update(this._eventsFiredAtom, (events) => [...events, key]);
|
|
971
|
+
}
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
return yield* this._activateModulesForEvent(key, modules, activatingEvents);
|
|
976
|
+
}).pipe(
|
|
977
|
+
Effect.ensuring(
|
|
978
|
+
Effect.all([
|
|
979
|
+
this._untrackFiber(this._inFlightFibers, fiber),
|
|
980
|
+
Ref.update(this._activatingEvents, (activating) => Array.filter(activating, (event) => event !== key)),
|
|
981
|
+
]),
|
|
982
|
+
),
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
private _activateModulesForEvent(
|
|
987
|
+
key: string,
|
|
988
|
+
modules: Plugin.PluginModule[],
|
|
989
|
+
activatingEvents: string[],
|
|
990
|
+
): Effect.Effect<boolean, Error> {
|
|
991
|
+
const activatingModuleIds = modules.map((module) => module.id);
|
|
992
|
+
return Effect.gen(this, function* () {
|
|
993
|
+
yield* Ref.update(this._activatingModules, (activating) => Array.appendAll(activating, activatingModuleIds));
|
|
994
|
+
|
|
995
|
+
log('activating modules', { key, modules: activatingModuleIds });
|
|
996
|
+
performance.mark(`event:${key}:start`);
|
|
997
|
+
yield* PubSub.publish(this.activation, { event: key, state: 'activating' });
|
|
998
|
+
|
|
999
|
+
yield* this._activateRelatedEvents(key, this._getBeforeEvents(modules, activatingEvents), 'before');
|
|
1000
|
+
|
|
1001
|
+
const capabilities = yield* this._loadCapabilitiesForModules(key, modules);
|
|
1002
|
+
yield* this._contributeCapabilitiesForModules(modules, capabilities);
|
|
1003
|
+
|
|
1004
|
+
yield* this._activateRelatedEvents(key, this._getAfterEvents(modules, activatingEvents), 'after');
|
|
1005
|
+
|
|
1006
|
+
if (!this._get(this._eventsFiredAtom).includes(key)) {
|
|
1007
|
+
this._update(this._eventsFiredAtom, (events) => [...events, key]);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
performance.mark(`event:${key}:end`);
|
|
1011
|
+
performance.measure(`event:${key}`, `event:${key}:start`, `event:${key}:end`);
|
|
1012
|
+
yield* PubSub.publish(this.activation, { event: key, state: 'activated' });
|
|
1013
|
+
log('activated', { key });
|
|
1014
|
+
|
|
1015
|
+
return true;
|
|
1016
|
+
}).pipe(
|
|
1017
|
+
Effect.ensuring(
|
|
1018
|
+
Ref.update(this._activatingModules, (activating) =>
|
|
1019
|
+
Array.filter(activating, (module) => !activatingModuleIds.includes(module)),
|
|
1020
|
+
),
|
|
1021
|
+
),
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
private _getModulesForActivation(
|
|
1026
|
+
key: string,
|
|
1027
|
+
activatingEvents: string[],
|
|
1028
|
+
activatingModules: string[],
|
|
1029
|
+
): Plugin.PluginModule[] {
|
|
1030
|
+
return this._getInactiveModulesByEvent(key).filter((module) => {
|
|
1031
|
+
const allOf = ActivationEvent.isAllOf(module.activatesOn);
|
|
1032
|
+
if (!allOf) {
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Check to see if all of the events in the `allOf` have been fired.
|
|
1037
|
+
// An event can be considered "fired" if it is in the `eventsFired` list or if it is currently being activated.
|
|
1038
|
+
const events = ActivationEvent.getEvents(module.activatesOn).filter(
|
|
1039
|
+
(event) => ActivationEvent.eventKey(event) !== key,
|
|
1040
|
+
);
|
|
1041
|
+
return (
|
|
1042
|
+
events.every(
|
|
1043
|
+
(event) =>
|
|
1044
|
+
this._get(this._eventsFiredAtom).includes(ActivationEvent.eventKey(event)) ||
|
|
1045
|
+
activatingEvents.includes(ActivationEvent.eventKey(event)),
|
|
1046
|
+
) && !activatingModules.includes(module.id)
|
|
1047
|
+
);
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
private _getBeforeEvents(
|
|
1052
|
+
modules: Plugin.PluginModule[],
|
|
1053
|
+
activatingEvents: string[],
|
|
1054
|
+
): ActivationEvent.ActivationEvent[] {
|
|
1055
|
+
return Function.pipe(
|
|
1056
|
+
modules,
|
|
1057
|
+
Array.flatMap((module) => module.firesBeforeActivation ?? []),
|
|
1058
|
+
HashSet.fromIterable,
|
|
1059
|
+
HashSet.toValues,
|
|
1060
|
+
Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private _getAfterEvents(
|
|
1065
|
+
modules: Plugin.PluginModule[],
|
|
1066
|
+
activatingEvents: string[],
|
|
1067
|
+
): ActivationEvent.ActivationEvent[] {
|
|
1068
|
+
return Function.pipe(
|
|
1069
|
+
modules,
|
|
1070
|
+
Array.flatMap((module) => module.firesAfterActivation ?? []),
|
|
1071
|
+
HashSet.fromIterable,
|
|
1072
|
+
HashSet.toValues,
|
|
1073
|
+
Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
private _activateRelatedEvents(
|
|
1078
|
+
key: string,
|
|
1079
|
+
events: ActivationEvent.ActivationEvent[],
|
|
1080
|
+
phase: 'before' | 'after',
|
|
1081
|
+
): Effect.Effect<void, Error> {
|
|
1082
|
+
const logLabel = phase === 'before' ? 'firesBeforeActivation' : 'firesAfterActivation';
|
|
1083
|
+
const eventKey = phase === 'before' ? 'beforeEvents' : 'afterEvents';
|
|
1084
|
+
return Function.pipe(
|
|
1085
|
+
events,
|
|
1086
|
+
Array.map((event) => this.activate(event, phase === 'before' ? { before: key } : { after: key })),
|
|
1087
|
+
Effect.allWith({ concurrency: 'unbounded' }),
|
|
1088
|
+
together(
|
|
1089
|
+
Effect.sleep(Duration.seconds(10)).pipe(
|
|
1090
|
+
Effect.andThen(
|
|
1091
|
+
Effect.sync(() =>
|
|
1092
|
+
log.warn(`${logLabel} is taking a long time`, {
|
|
1093
|
+
event: key,
|
|
1094
|
+
[eventKey]: events.map(ActivationEvent.eventKey),
|
|
1095
|
+
}),
|
|
1096
|
+
),
|
|
1097
|
+
),
|
|
1098
|
+
),
|
|
1099
|
+
),
|
|
1100
|
+
Effect.asVoid,
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
//
|
|
1105
|
+
// Module lifecycle helpers
|
|
1106
|
+
//
|
|
1107
|
+
|
|
1108
|
+
private _loadCapabilitiesForModules(
|
|
1109
|
+
key: string,
|
|
1110
|
+
modules: Plugin.PluginModule[],
|
|
1111
|
+
): Effect.Effect<Capability.Any[][], Error> {
|
|
1112
|
+
return Function.pipe(
|
|
1113
|
+
modules,
|
|
1114
|
+
Array.map((mod) => this._loadModule(mod, key)),
|
|
1115
|
+
Effect.allWith({ concurrency: 'unbounded' }),
|
|
1116
|
+
Effect.catchAll((error) => {
|
|
1117
|
+
return Effect.gen(this, function* () {
|
|
1118
|
+
yield* PubSub.publish(this.activation, { event: key, state: 'error', error });
|
|
1119
|
+
return yield* Effect.fail(error);
|
|
1120
|
+
});
|
|
1121
|
+
}),
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
private _contributeCapabilitiesForModules(
|
|
1126
|
+
modules: Plugin.PluginModule[],
|
|
1127
|
+
capabilities: Capability.Any[][],
|
|
1128
|
+
): Effect.Effect<void, Error> {
|
|
1129
|
+
return Function.pipe(
|
|
1130
|
+
modules,
|
|
1131
|
+
Array.zip(capabilities),
|
|
1132
|
+
Array.map(([module, capabilitySet]) => this._contributeCapabilities(module, capabilitySet)),
|
|
1133
|
+
// TODO(wittjosiah): This currently can't be run in parallel, and inserting
|
|
1134
|
+
// any yield between contributions (`Effect.yieldNow()`, `Effect.sleep(0)`)
|
|
1135
|
+
// races the `allOf` activation-event resolver — observed as a System
|
|
1136
|
+
// Error dialog on warm reloads. Contributions must stay strictly
|
|
1137
|
+
// synchronous within an event; React paint slots have to be found at
|
|
1138
|
+
// event boundaries higher up the call chain.
|
|
1139
|
+
Effect.all,
|
|
1140
|
+
Effect.asVoid,
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
563
1144
|
private _getModuleSemaphore(moduleId: Plugin.PluginModule['id']): Effect.Semaphore {
|
|
564
1145
|
let semaphore = this._moduleSemaphores.get(moduleId);
|
|
565
1146
|
if (!semaphore) {
|
|
@@ -569,7 +1150,14 @@ class ManagerImpl implements PluginManager {
|
|
|
569
1150
|
return semaphore;
|
|
570
1151
|
}
|
|
571
1152
|
|
|
572
|
-
|
|
1153
|
+
// `parentEvent` is the activation event that first triggered this module
|
|
1154
|
+
// load — included in `activating`/`activated` PubSub messages so subscribers
|
|
1155
|
+
// (e.g. the boot loader's status listener) can associate a module with its
|
|
1156
|
+
// triggering event in the trace. The same module may be referenced by
|
|
1157
|
+
// multiple events, but module loads are memoized via `_moduleMemoMap`, so
|
|
1158
|
+
// only the first event to need it will appear here; later events await the
|
|
1159
|
+
// cached deferred without re-publishing.
|
|
1160
|
+
private _loadModule = (module: Plugin.PluginModule, parentEvent: string): Effect.Effect<Capability.Any[], Error> =>
|
|
573
1161
|
Effect.gen(this, function* () {
|
|
574
1162
|
const semaphore = this._getModuleSemaphore(module.id);
|
|
575
1163
|
|
|
@@ -585,18 +1173,34 @@ class ManagerImpl implements PluginManager {
|
|
|
585
1173
|
this._moduleMemoMap.set(module.id, deferred);
|
|
586
1174
|
|
|
587
1175
|
const loadEffect = Effect.gen(this, function* () {
|
|
588
|
-
log('loading module', { module: module.id });
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1176
|
+
log('loading module', { module: module.id, parentEvent });
|
|
1177
|
+
performance.mark(`module:${module.id}:start`);
|
|
1178
|
+
yield* PubSub.publish(this.activation, { event: parentEvent, state: 'activating', module: module.id });
|
|
1179
|
+
const pluginId = this._getPluginIdForModule(module.id);
|
|
1180
|
+
const [duration, capabilities] = yield* module.activate().pipe(
|
|
1181
|
+
Effect.provideService(Capability.Service, this.capabilities),
|
|
1182
|
+
Effect.provideService(Plugin.Service, this),
|
|
1183
|
+
// Cap activation so a single misbehaving module can't hold the
|
|
1184
|
+
// event chain open. On timeout the failure is recorded against
|
|
1185
|
+
// the plugin and surfaced as `PluginTimeoutError`.
|
|
1186
|
+
Effect.timeoutFail({
|
|
1187
|
+
duration: this._activationTimeout,
|
|
1188
|
+
onTimeout: () =>
|
|
1189
|
+
new PluginTimeoutError({
|
|
1190
|
+
context: { id: pluginId ?? module.id, module: module.id, phase: 'activation' as PluginFailurePhase },
|
|
1191
|
+
}),
|
|
1192
|
+
}),
|
|
1193
|
+
Effect.timed,
|
|
1194
|
+
);
|
|
596
1195
|
const normalized = capabilities == null ? [] : Array.isArray(capabilities) ? capabilities : [capabilities];
|
|
1196
|
+
const elapsed = Duration.toMillis(duration);
|
|
1197
|
+
performance.mark(`module:${module.id}:end`);
|
|
1198
|
+
performance.measure(`module:${module.id}`, `module:${module.id}:start`, `module:${module.id}:end`);
|
|
1199
|
+
yield* PubSub.publish(this.activation, { event: parentEvent, state: 'activated', module: module.id });
|
|
597
1200
|
log('loaded module', {
|
|
598
1201
|
module: module.id,
|
|
599
|
-
|
|
1202
|
+
parentEvent,
|
|
1203
|
+
elapsed,
|
|
600
1204
|
failed: false,
|
|
601
1205
|
});
|
|
602
1206
|
return normalized as Capability.Any[];
|
|
@@ -609,10 +1213,19 @@ class ManagerImpl implements PluginManager {
|
|
|
609
1213
|
),
|
|
610
1214
|
),
|
|
611
1215
|
),
|
|
1216
|
+
Performance.addTrackEntry({
|
|
1217
|
+
name: module.id,
|
|
1218
|
+
devtools: {
|
|
1219
|
+
dataType: 'track-entry',
|
|
1220
|
+
track: 'Module Activation',
|
|
1221
|
+
trackGroup: 'Composer',
|
|
1222
|
+
color: 'primary',
|
|
1223
|
+
},
|
|
1224
|
+
}),
|
|
612
1225
|
);
|
|
613
1226
|
|
|
614
1227
|
// Fork the load to run in background, completing the deferred when done.
|
|
615
|
-
yield* Effect.forkDaemon(
|
|
1228
|
+
const fiber = yield* Effect.forkDaemon(
|
|
616
1229
|
loadEffect.pipe(
|
|
617
1230
|
Effect.tap((result) => Deferred.succeed(deferred, result)),
|
|
618
1231
|
Effect.catchAllCause((cause) => {
|
|
@@ -623,10 +1236,20 @@ class ManagerImpl implements PluginManager {
|
|
|
623
1236
|
stack: error instanceof Error ? error.stack : undefined,
|
|
624
1237
|
isDefect: !Cause.isFailure(cause),
|
|
625
1238
|
});
|
|
626
|
-
|
|
1239
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
1240
|
+
const pluginId = this._getPluginIdForModule(module.id);
|
|
1241
|
+
if (pluginId !== undefined) {
|
|
1242
|
+
this._recordFailure(pluginId, 'activation', normalizedError);
|
|
1243
|
+
this._scheduleAutoDisable(pluginId);
|
|
1244
|
+
}
|
|
1245
|
+
return Deferred.fail(deferred, normalizedError);
|
|
627
1246
|
}),
|
|
628
1247
|
),
|
|
629
1248
|
);
|
|
1249
|
+
yield* this._trackFiber(this._inFlightFibers, fiber);
|
|
1250
|
+
yield* Effect.forkDaemon(
|
|
1251
|
+
Fiber.await(fiber).pipe(Effect.andThen(() => this._untrackFiber(this._inFlightFibers, fiber))),
|
|
1252
|
+
);
|
|
630
1253
|
|
|
631
1254
|
return deferred;
|
|
632
1255
|
}).pipe(semaphore.withPermits(1));
|
|
@@ -680,6 +1303,22 @@ class ManagerImpl implements PluginManager {
|
|
|
680
1303
|
*/
|
|
681
1304
|
export const make = (options: ManagerOptions): PluginManager => new ManagerImpl(options);
|
|
682
1305
|
|
|
1306
|
+
/**
|
|
1307
|
+
* True when `error` (or anything along its `cause` chain) is a
|
|
1308
|
+
* {@link PluginTimeoutError}. Lazy-load timeouts wrap the timeout inside
|
|
1309
|
+
* `LazyPluginError`, so a shallow check on the outer error misses them.
|
|
1310
|
+
* Bounded depth so a circular chain can't loop forever.
|
|
1311
|
+
*/
|
|
1312
|
+
const isTimeoutCause = (error: unknown, depth = 0): boolean => {
|
|
1313
|
+
if (depth > 5 || !(error instanceof Error)) {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
if (PluginTimeoutError.is(error)) {
|
|
1317
|
+
return true;
|
|
1318
|
+
}
|
|
1319
|
+
return isTimeoutCause((error as Error & { cause?: unknown }).cause, depth + 1);
|
|
1320
|
+
};
|
|
1321
|
+
|
|
683
1322
|
/**
|
|
684
1323
|
* Runs an effect concurrently with another effect.
|
|
685
1324
|
* If the first effect completes, the second effect is interrupted.
|