@dxos/app-framework 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (571) hide show
  1. package/.storybook/main.mts +2 -4
  2. package/.storybook/preview.mts +2 -2
  3. package/dist/lib/browser/capability-Q5XRXRD2.mjs +38 -0
  4. package/dist/lib/browser/capability-Q5XRXRD2.mjs.map +7 -0
  5. package/dist/lib/browser/capability-V7LR4LQN.mjs +35 -0
  6. package/dist/lib/browser/capability-V7LR4LQN.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-23D4SJUE.mjs +42 -0
  8. package/dist/lib/browser/chunk-23D4SJUE.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-3JWJXGLK.mjs +79 -0
  10. package/dist/lib/browser/chunk-3JWJXGLK.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-3ZS2A3DN.mjs +907 -0
  12. package/dist/lib/browser/chunk-3ZS2A3DN.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-45CHLTBV.mjs +34 -0
  14. package/dist/lib/browser/chunk-45CHLTBV.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-5LAIGWLU.mjs +467 -0
  16. package/dist/lib/browser/chunk-5LAIGWLU.mjs.map +7 -0
  17. package/dist/lib/browser/chunk-66IXTIVK.mjs +48 -0
  18. package/dist/lib/browser/chunk-66IXTIVK.mjs.map +7 -0
  19. package/dist/lib/browser/chunk-FJ4765WW.mjs +8 -0
  20. package/dist/lib/browser/chunk-FJ4765WW.mjs.map +7 -0
  21. package/dist/lib/browser/chunk-G7SDBRKH.mjs +1 -0
  22. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  23. package/dist/lib/browser/chunk-JXCBZSBJ.mjs +372 -0
  24. package/dist/lib/browser/chunk-JXCBZSBJ.mjs.map +7 -0
  25. package/dist/lib/browser/chunk-MX5DKEJH.mjs +584 -0
  26. package/dist/lib/browser/chunk-MX5DKEJH.mjs.map +7 -0
  27. package/dist/lib/browser/chunk-WBHCSOBW.mjs +80 -0
  28. package/dist/lib/browser/chunk-WBHCSOBW.mjs.map +7 -0
  29. package/dist/lib/browser/chunk-Z55LVAGN.mjs +213 -0
  30. package/dist/lib/browser/chunk-Z55LVAGN.mjs.map +7 -0
  31. package/dist/lib/browser/chunk-ZGJAZSNE.mjs +142 -0
  32. package/dist/lib/browser/chunk-ZGJAZSNE.mjs.map +7 -0
  33. package/dist/lib/browser/cli/index.mjs +74 -0
  34. package/dist/lib/browser/cli/index.mjs.map +7 -0
  35. package/dist/lib/browser/common/activation-events.mjs +24 -0
  36. package/dist/lib/browser/common/capabilities.mjs +46 -0
  37. package/dist/lib/browser/core/activation-event.mjs +20 -0
  38. package/dist/lib/browser/core/activation-event.mjs.map +7 -0
  39. package/dist/lib/browser/core/capability.mjs +30 -0
  40. package/dist/lib/browser/core/capability.mjs.map +7 -0
  41. package/dist/lib/browser/core/plugin-manager.mjs +17 -0
  42. package/dist/lib/browser/core/plugin-manager.mjs.map +7 -0
  43. package/dist/lib/browser/core/plugin.mjs +37 -0
  44. package/dist/lib/browser/core/plugin.mjs.map +7 -0
  45. package/dist/lib/browser/core/url-loader.mjs +20 -0
  46. package/dist/lib/browser/core/url-loader.mjs.map +7 -0
  47. package/dist/lib/browser/index.mjs +95 -148
  48. package/dist/lib/browser/index.mjs.map +4 -4
  49. package/dist/lib/browser/invoker-capability-LNX4CGIV.mjs +44 -0
  50. package/dist/lib/browser/invoker-capability-LNX4CGIV.mjs.map +7 -0
  51. package/dist/lib/browser/meta.json +1 -1
  52. package/dist/lib/browser/testing/index.mjs +227 -41
  53. package/dist/lib/browser/testing/index.mjs.map +4 -4
  54. package/dist/lib/browser/testing/react.mjs +78 -0
  55. package/dist/lib/browser/testing/react.mjs.map +7 -0
  56. package/dist/lib/browser/ui/index.mjs +48 -0
  57. package/dist/lib/browser/ui/index.mjs.map +7 -0
  58. package/dist/lib/node-esm/capability-EW5GJCI6.mjs +39 -0
  59. package/dist/lib/node-esm/capability-EW5GJCI6.mjs.map +7 -0
  60. package/dist/lib/node-esm/capability-YKBMMD53.mjs +36 -0
  61. package/dist/lib/node-esm/capability-YKBMMD53.mjs.map +7 -0
  62. package/dist/lib/node-esm/chunk-37Z53PXZ.mjs +10 -0
  63. package/dist/lib/node-esm/chunk-37Z53PXZ.mjs.map +7 -0
  64. package/dist/lib/node-esm/chunk-6XW6LET6.mjs +35 -0
  65. package/dist/lib/node-esm/chunk-6XW6LET6.mjs.map +7 -0
  66. package/dist/lib/node-esm/chunk-D347W3KO.mjs +143 -0
  67. package/dist/lib/node-esm/chunk-D347W3KO.mjs.map +7 -0
  68. package/dist/lib/node-esm/chunk-D5PO2WXX.mjs +373 -0
  69. package/dist/lib/node-esm/chunk-D5PO2WXX.mjs.map +7 -0
  70. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  71. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  72. package/dist/lib/node-esm/chunk-HTBJU5FX.mjs +214 -0
  73. package/dist/lib/node-esm/chunk-HTBJU5FX.mjs.map +7 -0
  74. package/dist/lib/node-esm/chunk-KM2F6GH6.mjs +468 -0
  75. package/dist/lib/node-esm/chunk-KM2F6GH6.mjs.map +7 -0
  76. package/dist/lib/node-esm/chunk-OZ7DZA5Z.mjs +2 -0
  77. package/dist/lib/node-esm/chunk-OZ7DZA5Z.mjs.map +7 -0
  78. package/dist/lib/node-esm/chunk-Q7XBFII4.mjs +908 -0
  79. package/dist/lib/node-esm/chunk-Q7XBFII4.mjs.map +7 -0
  80. package/dist/lib/node-esm/chunk-SBS2YMPT.mjs +43 -0
  81. package/dist/lib/node-esm/chunk-SBS2YMPT.mjs.map +7 -0
  82. package/dist/lib/node-esm/chunk-SDJ4B2LU.mjs +80 -0
  83. package/dist/lib/node-esm/chunk-SDJ4B2LU.mjs.map +7 -0
  84. package/dist/lib/node-esm/chunk-WFSRZKBP.mjs +81 -0
  85. package/dist/lib/node-esm/chunk-WFSRZKBP.mjs.map +7 -0
  86. package/dist/lib/node-esm/chunk-WKTLE7MG.mjs +585 -0
  87. package/dist/lib/node-esm/chunk-WKTLE7MG.mjs.map +7 -0
  88. package/dist/lib/node-esm/chunk-XOCUANHO.mjs +49 -0
  89. package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +7 -0
  90. package/dist/lib/node-esm/cli/index.mjs +75 -0
  91. package/dist/lib/node-esm/cli/index.mjs.map +7 -0
  92. package/dist/lib/node-esm/common/activation-events.mjs +25 -0
  93. package/dist/lib/node-esm/common/activation-events.mjs.map +7 -0
  94. package/dist/lib/node-esm/common/capabilities.mjs +47 -0
  95. package/dist/lib/node-esm/common/capabilities.mjs.map +7 -0
  96. package/dist/lib/node-esm/core/activation-event.mjs +21 -0
  97. package/dist/lib/node-esm/core/activation-event.mjs.map +7 -0
  98. package/dist/lib/node-esm/core/capability.mjs +31 -0
  99. package/dist/lib/node-esm/core/capability.mjs.map +7 -0
  100. package/dist/lib/node-esm/core/plugin-manager.mjs +18 -0
  101. package/dist/lib/node-esm/core/plugin-manager.mjs.map +7 -0
  102. package/dist/lib/node-esm/core/plugin.mjs +38 -0
  103. package/dist/lib/node-esm/core/plugin.mjs.map +7 -0
  104. package/dist/lib/node-esm/core/url-loader.mjs +21 -0
  105. package/dist/lib/node-esm/core/url-loader.mjs.map +7 -0
  106. package/dist/lib/node-esm/index.mjs +95 -148
  107. package/dist/lib/node-esm/index.mjs.map +4 -4
  108. package/dist/lib/node-esm/invoker-capability-O4T5PHLA.mjs +45 -0
  109. package/dist/lib/node-esm/invoker-capability-O4T5PHLA.mjs.map +7 -0
  110. package/dist/lib/node-esm/meta.json +1 -1
  111. package/dist/lib/node-esm/testing/index.mjs +227 -41
  112. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  113. package/dist/lib/node-esm/testing/react.mjs +79 -0
  114. package/dist/lib/node-esm/testing/react.mjs.map +7 -0
  115. package/dist/lib/node-esm/ui/index.mjs +49 -0
  116. package/dist/lib/node-esm/ui/index.mjs.map +7 -0
  117. package/dist/plugin/node-esm/index.mjs +832 -0
  118. package/dist/plugin/node-esm/index.mjs.map +7 -0
  119. package/dist/plugin/node-esm/meta.json +1 -0
  120. package/dist/types/src/cli/cli.d.ts +39 -0
  121. package/dist/types/src/cli/cli.d.ts.map +1 -0
  122. package/dist/types/src/cli/index.d.ts +2 -0
  123. package/dist/types/src/cli/index.d.ts.map +1 -0
  124. package/dist/types/src/common/activation-events.d.ts +27 -0
  125. package/dist/types/src/common/activation-events.d.ts.map +1 -0
  126. package/dist/types/src/common/capabilities.d.ts +110 -197
  127. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  128. package/dist/types/src/common/index.d.ts +4 -8
  129. package/dist/types/src/common/index.d.ts.map +1 -1
  130. package/dist/types/src/common/operations.d.ts +19 -0
  131. package/dist/types/src/common/operations.d.ts.map +1 -0
  132. package/dist/types/src/common/translations.d.ts +8 -8
  133. package/dist/types/src/common/translations.d.ts.map +1 -1
  134. package/dist/types/src/context.d.ts +5 -0
  135. package/dist/types/src/context.d.ts.map +1 -0
  136. package/dist/types/src/core/{events.d.ts → activation-event.d.ts} +11 -11
  137. package/dist/types/src/core/activation-event.d.ts.map +1 -0
  138. package/dist/types/src/core/capability-manager.d.ts +48 -0
  139. package/dist/types/src/core/capability-manager.d.ts.map +1 -0
  140. package/dist/types/src/core/capability-manager.test.d.ts +2 -0
  141. package/dist/types/src/core/capability-manager.test.d.ts.map +1 -0
  142. package/dist/types/src/core/capability.d.ts +156 -0
  143. package/dist/types/src/core/capability.d.ts.map +1 -0
  144. package/dist/types/src/core/index.d.ts +8 -4
  145. package/dist/types/src/core/index.d.ts.map +1 -1
  146. package/dist/types/src/core/plugin-asset-cache.d.ts +71 -0
  147. package/dist/types/src/core/plugin-asset-cache.d.ts.map +1 -0
  148. package/dist/types/src/core/plugin-manager.d.ts +122 -0
  149. package/dist/types/src/core/plugin-manager.d.ts.map +1 -0
  150. package/dist/types/src/core/plugin-manager.test.d.ts +2 -0
  151. package/dist/types/src/core/plugin-manager.test.d.ts.map +1 -0
  152. package/dist/types/src/core/plugin-manifest.d.ts +76 -0
  153. package/dist/types/src/core/plugin-manifest.d.ts.map +1 -0
  154. package/dist/types/src/core/plugin-manifest.test.d.ts +2 -0
  155. package/dist/types/src/core/plugin-manifest.test.d.ts.map +1 -0
  156. package/dist/types/src/core/plugin.d.ts +207 -39
  157. package/dist/types/src/core/plugin.d.ts.map +1 -1
  158. package/dist/types/src/core/url-loader.d.ts +112 -0
  159. package/dist/types/src/core/url-loader.d.ts.map +1 -0
  160. package/dist/types/src/core/url-loader.test.d.ts +2 -0
  161. package/dist/types/src/core/url-loader.test.d.ts.map +1 -0
  162. package/dist/types/src/helpers.d.ts.map +1 -1
  163. package/dist/types/src/index.d.ts +3 -4
  164. package/dist/types/src/index.d.ts.map +1 -1
  165. package/dist/types/src/plugin-operation/OperationPlugin.d.ts +3 -0
  166. package/dist/types/src/plugin-operation/OperationPlugin.d.ts.map +1 -0
  167. package/dist/types/src/plugin-operation/history/capability.d.ts +7 -0
  168. package/dist/types/src/plugin-operation/history/capability.d.ts.map +1 -0
  169. package/dist/types/src/plugin-operation/history/errors.d.ts +32 -0
  170. package/dist/types/src/plugin-operation/history/errors.d.ts.map +1 -0
  171. package/dist/types/src/plugin-operation/history/history-tracker.d.ts +18 -0
  172. package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +1 -0
  173. package/dist/types/src/plugin-operation/history/history-tracker.test.d.ts +2 -0
  174. package/dist/types/src/plugin-operation/history/history-tracker.test.d.ts.map +1 -0
  175. package/dist/types/src/plugin-operation/history/index.d.ts +6 -0
  176. package/dist/types/src/plugin-operation/history/index.d.ts.map +1 -0
  177. package/dist/types/src/plugin-operation/history/types.d.ts +13 -0
  178. package/dist/types/src/plugin-operation/history/types.d.ts.map +1 -0
  179. package/dist/types/src/plugin-operation/history/undo-mapping.d.ts +101 -0
  180. package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +1 -0
  181. package/dist/types/src/plugin-operation/history/undo-registry.d.ts +23 -0
  182. package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +1 -0
  183. package/dist/types/src/plugin-operation/history/undo-registry.test.d.ts +2 -0
  184. package/dist/types/src/plugin-operation/history/undo-registry.test.d.ts.map +1 -0
  185. package/dist/types/src/plugin-operation/index.d.ts +3 -0
  186. package/dist/types/src/plugin-operation/index.d.ts.map +1 -0
  187. package/dist/types/src/plugin-operation/invoker-capability.d.ts +6 -0
  188. package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +1 -0
  189. package/dist/types/src/plugin-operation/meta.d.ts +3 -0
  190. package/dist/types/src/plugin-operation/meta.d.ts.map +1 -0
  191. package/dist/types/src/plugin-operation/testing.d.ts +59 -0
  192. package/dist/types/src/plugin-operation/testing.d.ts.map +1 -0
  193. package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts +3 -0
  194. package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts.map +1 -0
  195. package/dist/types/src/plugin-runtime/capability.d.ts +6 -0
  196. package/dist/types/src/plugin-runtime/capability.d.ts.map +1 -0
  197. package/dist/types/src/plugin-runtime/index.d.ts +2 -0
  198. package/dist/types/src/plugin-runtime/index.d.ts.map +1 -0
  199. package/dist/types/src/plugin-runtime/meta.d.ts +3 -0
  200. package/dist/types/src/plugin-runtime/meta.d.ts.map +1 -0
  201. package/dist/types/src/testing/harness.d.ts +67 -0
  202. package/dist/types/src/testing/harness.d.ts.map +1 -0
  203. package/dist/types/src/testing/index.d.ts +2 -0
  204. package/dist/types/src/testing/index.d.ts.map +1 -1
  205. package/dist/types/src/testing/react.d.ts +27 -0
  206. package/dist/types/src/testing/react.d.ts.map +1 -0
  207. package/dist/types/src/testing/react.test.d.ts +2 -0
  208. package/dist/types/src/testing/react.test.d.ts.map +1 -0
  209. package/dist/types/src/testing/service.d.ts +8 -0
  210. package/dist/types/src/testing/service.d.ts.map +1 -0
  211. package/dist/types/src/testing/withPluginManager.d.ts +7 -6
  212. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  213. package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
  214. package/dist/types/src/ui/components/App/App.d.ts +9 -0
  215. package/dist/types/src/ui/components/App/App.d.ts.map +1 -0
  216. package/dist/types/src/ui/components/App/App.stories.d.ts +19 -0
  217. package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -0
  218. package/dist/types/src/ui/components/App/index.d.ts +2 -0
  219. package/dist/types/src/ui/components/App/index.d.ts.map +1 -0
  220. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts +64 -0
  221. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts.map +1 -0
  222. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts +19 -0
  223. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts.map +1 -0
  224. package/dist/types/src/ui/components/Placeholder/index.d.ts +2 -0
  225. package/dist/types/src/ui/components/Placeholder/index.d.ts.map +1 -0
  226. package/dist/types/src/{playground/playground.stories.d.ts → ui/components/PluginManager/PluginManagerContext.stories.d.ts} +5 -3
  227. package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -0
  228. package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts +10 -0
  229. package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts.map +1 -0
  230. package/dist/types/src/ui/components/PluginManager/index.d.ts +2 -0
  231. package/dist/types/src/ui/components/PluginManager/index.d.ts.map +1 -0
  232. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +24 -0
  233. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -0
  234. package/dist/types/src/{components/App.stories.d.ts → ui/components/Surface/SurfaceComponent.stories.d.ts} +1 -1
  235. package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -0
  236. package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts +11 -0
  237. package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +1 -0
  238. package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts +48 -0
  239. package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -0
  240. package/dist/types/src/ui/components/Surface/context.d.ts +5 -0
  241. package/dist/types/src/ui/components/Surface/context.d.ts.map +1 -0
  242. package/dist/types/src/ui/components/Surface/index.d.ts +36 -0
  243. package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -0
  244. package/dist/types/src/ui/components/Surface/types.d.ts +197 -0
  245. package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -0
  246. package/dist/types/src/ui/components/Surface/types.test.d.ts +2 -0
  247. package/dist/types/src/ui/components/Surface/types.test.d.ts.map +1 -0
  248. package/dist/types/src/ui/components/index.d.ts +5 -0
  249. package/dist/types/src/ui/components/index.d.ts.map +1 -0
  250. package/dist/types/src/ui/hooks/index.d.ts +6 -0
  251. package/dist/types/src/ui/hooks/index.d.ts.map +1 -0
  252. package/dist/types/src/ui/hooks/useApp.d.ts +88 -0
  253. package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -0
  254. package/dist/types/src/ui/hooks/useApp.test.d.ts +2 -0
  255. package/dist/types/src/ui/hooks/useApp.test.d.ts.map +1 -0
  256. package/dist/types/src/ui/hooks/useCapabilities.d.ts +31 -0
  257. package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -0
  258. package/dist/types/src/{components → ui/hooks}/useLoading.d.ts +1 -2
  259. package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -0
  260. package/dist/types/src/ui/hooks/useSettingsState.d.ts +10 -0
  261. package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -0
  262. package/dist/types/src/ui/hooks/useSurface.d.ts +3 -0
  263. package/dist/types/src/ui/hooks/useSurface.d.ts.map +1 -0
  264. package/dist/types/src/ui/index.d.ts +3 -0
  265. package/dist/types/src/ui/index.d.ts.map +1 -0
  266. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +34 -0
  267. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +1 -0
  268. package/dist/types/src/vite-plugin/boot-loader/index.d.ts +52 -0
  269. package/dist/types/src/vite-plugin/boot-loader/index.d.ts.map +1 -0
  270. package/dist/types/src/vite-plugin/composer/index.d.ts +34 -0
  271. package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -0
  272. package/dist/types/src/vite-plugin/import-map/index.d.ts +28 -0
  273. package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -0
  274. package/dist/types/src/vite-plugin/index.d.ts +5 -0
  275. package/dist/types/src/vite-plugin/index.d.ts.map +1 -0
  276. package/dist/types/src/vite-plugin/manifest.d.ts +37 -0
  277. package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -0
  278. package/dist/types/src/vite-plugin/manifest.test.d.ts +2 -0
  279. package/dist/types/src/vite-plugin/manifest.test.d.ts.map +1 -0
  280. package/dist/types/src/vite-plugin/packages.d.ts +13 -0
  281. package/dist/types/src/vite-plugin/packages.d.ts.map +1 -0
  282. package/dist/types/tsconfig.tsbuildinfo +1 -1
  283. package/moon.yml +25 -6
  284. package/package.json +112 -55
  285. package/src/cli/cli.ts +107 -0
  286. package/src/{components → cli}/index.ts +1 -1
  287. package/src/common/activation-events.ts +44 -0
  288. package/src/common/capabilities.ts +169 -210
  289. package/src/common/index.ts +4 -8
  290. package/src/common/operations.ts +35 -0
  291. package/src/common/translations.ts +18 -10
  292. package/src/context.ts +9 -0
  293. package/src/core/{events.ts → activation-event.ts} +10 -7
  294. package/src/core/capability-manager.test.ts +151 -0
  295. package/src/core/capability-manager.ts +192 -0
  296. package/src/core/capability.ts +247 -0
  297. package/src/core/index.ts +8 -4
  298. package/src/core/plugin-asset-cache.ts +60 -0
  299. package/src/core/plugin-manager.test.ts +1354 -0
  300. package/src/core/plugin-manager.ts +1025 -0
  301. package/src/core/plugin-manifest.test.ts +48 -0
  302. package/src/core/plugin-manifest.ts +102 -0
  303. package/src/core/plugin.ts +365 -45
  304. package/src/core/url-loader.test.ts +178 -0
  305. package/src/core/url-loader.ts +337 -0
  306. package/src/index.ts +3 -4
  307. package/src/plugin-operation/OperationPlugin.ts +24 -0
  308. package/src/plugin-operation/history/capability.ts +36 -0
  309. package/src/plugin-operation/history/errors.ts +7 -0
  310. package/src/plugin-operation/history/history-tracker.test.ts +374 -0
  311. package/src/plugin-operation/history/history-tracker.ts +128 -0
  312. package/src/plugin-operation/history/index.ts +9 -0
  313. package/src/plugin-operation/history/types.ts +17 -0
  314. package/src/plugin-operation/history/undo-mapping.ts +135 -0
  315. package/src/plugin-operation/history/undo-registry.test.ts +72 -0
  316. package/src/plugin-operation/history/undo-registry.ts +54 -0
  317. package/src/plugin-operation/index.ts +6 -0
  318. package/src/plugin-operation/invoker-capability.ts +55 -0
  319. package/src/plugin-operation/meta.ts +11 -0
  320. package/src/plugin-operation/testing.ts +155 -0
  321. package/src/plugin-runtime/RuntimePlugin.ts +19 -0
  322. package/src/plugin-runtime/capability.ts +53 -0
  323. package/src/{playground/layout → plugin-runtime}/index.ts +1 -1
  324. package/src/plugin-runtime/meta.ts +11 -0
  325. package/src/testing/harness.ts +229 -0
  326. package/src/testing/index.ts +2 -0
  327. package/src/testing/react.test.tsx +48 -0
  328. package/src/testing/react.tsx +113 -0
  329. package/src/testing/service.ts +52 -0
  330. package/src/testing/withPluginManager.stories.tsx +8 -9
  331. package/src/testing/withPluginManager.tsx +68 -40
  332. package/src/ui/components/App/App.stories.tsx +92 -0
  333. package/src/ui/components/App/App.tsx +81 -0
  334. package/src/{playground/debug → ui/components/App}/index.ts +1 -1
  335. package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
  336. package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
  337. package/src/{playground/logger → ui/components/Placeholder}/index.ts +1 -1
  338. package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +185 -0
  339. package/src/{react → ui/components/PluginManager}/PluginManagerProvider.ts +3 -3
  340. package/src/ui/components/PluginManager/index.ts +5 -0
  341. package/src/ui/components/Surface/SurfaceComponent.stories.tsx +144 -0
  342. package/src/ui/components/Surface/SurfaceComponent.tsx +303 -0
  343. package/src/ui/components/Surface/SurfaceInfo.tsx +107 -0
  344. package/src/ui/components/Surface/SurfaceProfilerContext.tsx +207 -0
  345. package/src/ui/components/Surface/context.ts +12 -0
  346. package/src/ui/components/Surface/index.ts +54 -0
  347. package/src/ui/components/Surface/types.test.ts +126 -0
  348. package/src/ui/components/Surface/types.ts +269 -0
  349. package/src/ui/components/index.ts +8 -0
  350. package/src/ui/hooks/index.ts +9 -0
  351. package/src/ui/hooks/useApp.test.tsx +159 -0
  352. package/src/ui/hooks/useApp.tsx +413 -0
  353. package/src/ui/hooks/useCapabilities.ts +67 -0
  354. package/src/{components → ui/hooks}/useLoading.tsx +16 -10
  355. package/src/ui/hooks/useSettingsState.ts +26 -0
  356. package/src/ui/hooks/useSurface.ts +13 -0
  357. package/src/ui/index.ts +6 -0
  358. package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +263 -0
  359. package/src/vite-plugin/boot-loader/boot-loader.css +294 -0
  360. package/src/vite-plugin/boot-loader/boot-loader.js +274 -0
  361. package/src/vite-plugin/boot-loader/index.ts +112 -0
  362. package/src/vite-plugin/composer/index.ts +277 -0
  363. package/src/vite-plugin/import-map/index.ts +524 -0
  364. package/src/vite-plugin/index.ts +10 -0
  365. package/src/vite-plugin/manifest.test.ts +24 -0
  366. package/src/vite-plugin/manifest.ts +50 -0
  367. package/src/vite-plugin/packages.ts +188 -0
  368. package/tsconfig.json +18 -15
  369. package/tsconfig.node.json +2 -4
  370. package/typedoc.json +2 -4
  371. package/vitest.config.ts +1 -1
  372. package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
  373. package/.swc/plugins/linux_x86_64_19.0.0/fce1bdb8e20a094e4af08bad09cc81497ed0e2e7c51223b07d371063cca18429.wasmer-v7 +0 -0
  374. package/dist/lib/browser/app-graph-builder-XH4OYQLC.mjs +0 -137
  375. package/dist/lib/browser/app-graph-builder-XH4OYQLC.mjs.map +0 -7
  376. package/dist/lib/browser/chunk-6V54SRFL.mjs +0 -1638
  377. package/dist/lib/browser/chunk-6V54SRFL.mjs.map +0 -7
  378. package/dist/lib/browser/chunk-RGKMLI6U.mjs +0 -35
  379. package/dist/lib/browser/chunk-RGKMLI6U.mjs.map +0 -7
  380. package/dist/lib/browser/chunk-ZZVFNUHZ.mjs +0 -467
  381. package/dist/lib/browser/chunk-ZZVFNUHZ.mjs.map +0 -7
  382. package/dist/lib/browser/intent-dispatcher-VFMJVO2M.mjs +0 -11
  383. package/dist/lib/browser/intent-resolver-ICAPD4JL.mjs +0 -39
  384. package/dist/lib/browser/intent-resolver-ICAPD4JL.mjs.map +0 -7
  385. package/dist/lib/browser/store-7ZGMHOGB.mjs +0 -30
  386. package/dist/lib/browser/store-7ZGMHOGB.mjs.map +0 -7
  387. package/dist/lib/browser/worker.mjs +0 -77
  388. package/dist/lib/node-esm/app-graph-builder-C7H22SOL.mjs +0 -138
  389. package/dist/lib/node-esm/app-graph-builder-C7H22SOL.mjs.map +0 -7
  390. package/dist/lib/node-esm/chunk-AXSZKZFD.mjs +0 -468
  391. package/dist/lib/node-esm/chunk-AXSZKZFD.mjs.map +0 -7
  392. package/dist/lib/node-esm/chunk-LKPMRTRR.mjs +0 -37
  393. package/dist/lib/node-esm/chunk-LKPMRTRR.mjs.map +0 -7
  394. package/dist/lib/node-esm/chunk-SOVTUUAY.mjs +0 -1640
  395. package/dist/lib/node-esm/chunk-SOVTUUAY.mjs.map +0 -7
  396. package/dist/lib/node-esm/intent-dispatcher-SAPOKSLZ.mjs +0 -12
  397. package/dist/lib/node-esm/intent-resolver-CRNJ6BMD.mjs +0 -40
  398. package/dist/lib/node-esm/intent-resolver-CRNJ6BMD.mjs.map +0 -7
  399. package/dist/lib/node-esm/store-H4F4RMYD.mjs +0 -31
  400. package/dist/lib/node-esm/store-H4F4RMYD.mjs.map +0 -7
  401. package/dist/lib/node-esm/worker.mjs +0 -78
  402. package/dist/types/src/common/collaboration.d.ts +0 -20
  403. package/dist/types/src/common/collaboration.d.ts.map +0 -1
  404. package/dist/types/src/common/events.d.ts +0 -52
  405. package/dist/types/src/common/events.d.ts.map +0 -1
  406. package/dist/types/src/common/file.d.ts +0 -14
  407. package/dist/types/src/common/file.d.ts.map +0 -1
  408. package/dist/types/src/common/graph.d.ts +0 -21
  409. package/dist/types/src/common/graph.d.ts.map +0 -1
  410. package/dist/types/src/common/layout.d.ts +0 -279
  411. package/dist/types/src/common/layout.d.ts.map +0 -1
  412. package/dist/types/src/common/surface.d.ts +0 -59
  413. package/dist/types/src/common/surface.d.ts.map +0 -1
  414. package/dist/types/src/components/App.d.ts +0 -10
  415. package/dist/types/src/components/App.d.ts.map +0 -1
  416. package/dist/types/src/components/App.stories.d.ts.map +0 -1
  417. package/dist/types/src/components/DefaultFallback.d.ts +0 -8
  418. package/dist/types/src/components/DefaultFallback.d.ts.map +0 -1
  419. package/dist/types/src/components/index.d.ts +0 -2
  420. package/dist/types/src/components/index.d.ts.map +0 -1
  421. package/dist/types/src/components/useApp.d.ts +0 -44
  422. package/dist/types/src/components/useApp.d.ts.map +0 -1
  423. package/dist/types/src/components/useLoading.d.ts.map +0 -1
  424. package/dist/types/src/core/capabilities.d.ts +0 -117
  425. package/dist/types/src/core/capabilities.d.ts.map +0 -1
  426. package/dist/types/src/core/capabilities.test.d.ts +0 -2
  427. package/dist/types/src/core/capabilities.test.d.ts.map +0 -1
  428. package/dist/types/src/core/events.d.ts.map +0 -1
  429. package/dist/types/src/core/manager.d.ts +0 -126
  430. package/dist/types/src/core/manager.d.ts.map +0 -1
  431. package/dist/types/src/core/manager.test.d.ts +0 -2
  432. package/dist/types/src/core/manager.test.d.ts.map +0 -1
  433. package/dist/types/src/playground/debug/Debug.d.ts +0 -6
  434. package/dist/types/src/playground/debug/Debug.d.ts.map +0 -1
  435. package/dist/types/src/playground/debug/index.d.ts +0 -2
  436. package/dist/types/src/playground/debug/index.d.ts.map +0 -1
  437. package/dist/types/src/playground/debug/plugin.d.ts +0 -2
  438. package/dist/types/src/playground/debug/plugin.d.ts.map +0 -1
  439. package/dist/types/src/playground/generator/Main.d.ts +0 -6
  440. package/dist/types/src/playground/generator/Main.d.ts.map +0 -1
  441. package/dist/types/src/playground/generator/Toolbar.d.ts +0 -6
  442. package/dist/types/src/playground/generator/Toolbar.d.ts.map +0 -1
  443. package/dist/types/src/playground/generator/generator.d.ts +0 -7
  444. package/dist/types/src/playground/generator/generator.d.ts.map +0 -1
  445. package/dist/types/src/playground/generator/index.d.ts +0 -3
  446. package/dist/types/src/playground/generator/index.d.ts.map +0 -1
  447. package/dist/types/src/playground/generator/plugin.d.ts +0 -2
  448. package/dist/types/src/playground/generator/plugin.d.ts.map +0 -1
  449. package/dist/types/src/playground/layout/Layout.d.ts +0 -8
  450. package/dist/types/src/playground/layout/Layout.d.ts.map +0 -1
  451. package/dist/types/src/playground/layout/index.d.ts +0 -2
  452. package/dist/types/src/playground/layout/index.d.ts.map +0 -1
  453. package/dist/types/src/playground/layout/plugin.d.ts +0 -2
  454. package/dist/types/src/playground/layout/plugin.d.ts.map +0 -1
  455. package/dist/types/src/playground/logger/Toolbar.d.ts +0 -6
  456. package/dist/types/src/playground/logger/Toolbar.d.ts.map +0 -1
  457. package/dist/types/src/playground/logger/index.d.ts +0 -2
  458. package/dist/types/src/playground/logger/index.d.ts.map +0 -1
  459. package/dist/types/src/playground/logger/plugin.d.ts +0 -2
  460. package/dist/types/src/playground/logger/plugin.d.ts.map +0 -1
  461. package/dist/types/src/playground/logger/schema.d.ts +0 -13
  462. package/dist/types/src/playground/logger/schema.d.ts.map +0 -1
  463. package/dist/types/src/playground/playground.stories.d.ts.map +0 -1
  464. package/dist/types/src/plugin-intent/IntentPlugin.d.ts +0 -2
  465. package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +0 -1
  466. package/dist/types/src/plugin-intent/actions.d.ts +0 -36
  467. package/dist/types/src/plugin-intent/actions.d.ts.map +0 -1
  468. package/dist/types/src/plugin-intent/errors.d.ts +0 -16
  469. package/dist/types/src/plugin-intent/errors.d.ts.map +0 -1
  470. package/dist/types/src/plugin-intent/index.d.ts +0 -6
  471. package/dist/types/src/plugin-intent/index.d.ts.map +0 -1
  472. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +0 -139
  473. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +0 -1
  474. package/dist/types/src/plugin-intent/intent-dispatcher.test.d.ts +0 -2
  475. package/dist/types/src/plugin-intent/intent-dispatcher.test.d.ts.map +0 -1
  476. package/dist/types/src/plugin-intent/intent.d.ts +0 -63
  477. package/dist/types/src/plugin-intent/intent.d.ts.map +0 -1
  478. package/dist/types/src/plugin-intent/meta.d.ts +0 -3
  479. package/dist/types/src/plugin-intent/meta.d.ts.map +0 -1
  480. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts +0 -2
  481. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +0 -1
  482. package/dist/types/src/plugin-settings/actions.d.ts +0 -25
  483. package/dist/types/src/plugin-settings/actions.d.ts.map +0 -1
  484. package/dist/types/src/plugin-settings/app-graph-builder.d.ts +0 -4
  485. package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +0 -1
  486. package/dist/types/src/plugin-settings/index.d.ts +0 -3
  487. package/dist/types/src/plugin-settings/index.d.ts.map +0 -1
  488. package/dist/types/src/plugin-settings/intent-resolver.d.ts +0 -4
  489. package/dist/types/src/plugin-settings/intent-resolver.d.ts.map +0 -1
  490. package/dist/types/src/plugin-settings/meta.d.ts +0 -3
  491. package/dist/types/src/plugin-settings/meta.d.ts.map +0 -1
  492. package/dist/types/src/plugin-settings/store.d.ts +0 -5
  493. package/dist/types/src/plugin-settings/store.d.ts.map +0 -1
  494. package/dist/types/src/plugin-settings/translations.d.ts +0 -11
  495. package/dist/types/src/plugin-settings/translations.d.ts.map +0 -1
  496. package/dist/types/src/react/ErrorBoundary.d.ts +0 -30
  497. package/dist/types/src/react/ErrorBoundary.d.ts.map +0 -1
  498. package/dist/types/src/react/IntentContext.d.ts +0 -8
  499. package/dist/types/src/react/IntentContext.d.ts.map +0 -1
  500. package/dist/types/src/react/PluginManagerProvider.d.ts +0 -10
  501. package/dist/types/src/react/PluginManagerProvider.d.ts.map +0 -1
  502. package/dist/types/src/react/Surface.d.ts +0 -12
  503. package/dist/types/src/react/Surface.d.ts.map +0 -1
  504. package/dist/types/src/react/Surface.stories.d.ts +0 -17
  505. package/dist/types/src/react/Surface.stories.d.ts.map +0 -1
  506. package/dist/types/src/react/common.d.ts +0 -13
  507. package/dist/types/src/react/common.d.ts.map +0 -1
  508. package/dist/types/src/react/index.d.ts +0 -7
  509. package/dist/types/src/react/index.d.ts.map +0 -1
  510. package/dist/types/src/react/useCapabilities.d.ts +0 -13
  511. package/dist/types/src/react/useCapabilities.d.ts.map +0 -1
  512. package/dist/types/src/react/useIntentResolver.d.ts +0 -3
  513. package/dist/types/src/react/useIntentResolver.d.ts.map +0 -1
  514. package/dist/types/src/worker.d.ts +0 -4
  515. package/dist/types/src/worker.d.ts.map +0 -1
  516. package/src/common/collaboration.ts +0 -18
  517. package/src/common/events.ts +0 -79
  518. package/src/common/file.ts +0 -22
  519. package/src/common/graph.ts +0 -30
  520. package/src/common/layout.ts +0 -277
  521. package/src/common/surface.ts +0 -83
  522. package/src/components/App.stories.tsx +0 -33
  523. package/src/components/App.tsx +0 -59
  524. package/src/components/DefaultFallback.tsx +0 -26
  525. package/src/components/useApp.tsx +0 -164
  526. package/src/core/capabilities.test.ts +0 -136
  527. package/src/core/capabilities.ts +0 -259
  528. package/src/core/manager.test.ts +0 -516
  529. package/src/core/manager.ts +0 -597
  530. package/src/playground/debug/Debug.tsx +0 -39
  531. package/src/playground/debug/plugin.ts +0 -16
  532. package/src/playground/generator/Main.tsx +0 -71
  533. package/src/playground/generator/Toolbar.tsx +0 -47
  534. package/src/playground/generator/generator.ts +0 -48
  535. package/src/playground/generator/index.ts +0 -6
  536. package/src/playground/generator/plugin.ts +0 -22
  537. package/src/playground/layout/Layout.tsx +0 -33
  538. package/src/playground/layout/plugin.ts +0 -18
  539. package/src/playground/logger/Toolbar.tsx +0 -30
  540. package/src/playground/logger/plugin.ts +0 -41
  541. package/src/playground/logger/schema.ts +0 -12
  542. package/src/playground/playground.stories.tsx +0 -46
  543. package/src/plugin-intent/IntentPlugin.ts +0 -20
  544. package/src/plugin-intent/actions.ts +0 -31
  545. package/src/plugin-intent/errors.ts +0 -40
  546. package/src/plugin-intent/index.ts +0 -9
  547. package/src/plugin-intent/intent-dispatcher.test.ts +0 -279
  548. package/src/plugin-intent/intent-dispatcher.ts +0 -334
  549. package/src/plugin-intent/intent.ts +0 -154
  550. package/src/plugin-intent/meta.ts +0 -10
  551. package/src/plugin-settings/SettingsPlugin.ts +0 -34
  552. package/src/plugin-settings/actions.ts +0 -25
  553. package/src/plugin-settings/app-graph-builder.ts +0 -159
  554. package/src/plugin-settings/index.ts +0 -6
  555. package/src/plugin-settings/intent-resolver.ts +0 -35
  556. package/src/plugin-settings/meta.ts +0 -10
  557. package/src/plugin-settings/store.ts +0 -33
  558. package/src/plugin-settings/translations.ts +0 -19
  559. package/src/react/ErrorBoundary.tsx +0 -54
  560. package/src/react/IntentContext.tsx +0 -35
  561. package/src/react/Surface.stories.tsx +0 -101
  562. package/src/react/Surface.tsx +0 -86
  563. package/src/react/common.ts +0 -13
  564. package/src/react/index.ts +0 -10
  565. package/src/react/useCapabilities.ts +0 -31
  566. package/src/react/useIntentResolver.ts +0 -22
  567. package/src/worker.ts +0 -11
  568. /package/dist/lib/browser/{intent-dispatcher-VFMJVO2M.mjs.map → chunk-G7SDBRKH.mjs.map} +0 -0
  569. /package/dist/lib/browser/{worker.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
  570. /package/dist/lib/{node-esm/intent-dispatcher-SAPOKSLZ.mjs.map → browser/common/activation-events.mjs.map} +0 -0
  571. /package/dist/lib/{node-esm/worker.mjs.map → browser/common/capabilities.mjs.map} +0 -0
@@ -0,0 +1,1025 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Atom, Registry } from '@effect-atom/atom-react';
6
+ import * as Array from 'effect/Array';
7
+ import * as Cause from 'effect/Cause';
8
+ import * as Deferred from 'effect/Deferred';
9
+ import * as Duration from 'effect/Duration';
10
+ import * as Effect from 'effect/Effect';
11
+ import * as Fiber from 'effect/Fiber';
12
+ import * as Function from 'effect/Function';
13
+ import * as HashSet from 'effect/HashSet';
14
+ import * as PubSub from 'effect/PubSub';
15
+ import * as Ref from 'effect/Ref';
16
+
17
+ import { runAndForwardErrors } from '@dxos/effect';
18
+ import { Performance } from '@dxos/effect';
19
+ import { BaseError } from '@dxos/errors';
20
+ import { log } from '@dxos/log';
21
+
22
+ import * as ActivationEvent from './activation-event';
23
+ import * as Capability from './capability';
24
+ import * as CapabilityManager from './capability-manager';
25
+ import * as Plugin from './plugin';
26
+
27
+ /**
28
+ * Tagged error for failures during the constructor-launched core/enabled
29
+ * `enable()` chain. Surfaces via {@link PluginManager.activate}'s wait on
30
+ * `_initialization` so a caller blocked on initialization gets a typed
31
+ * failure (with the original error preserved as `cause`) instead of an
32
+ * untyped `Error`.
33
+ */
34
+ export class PluginInitializationError extends BaseError.extend(
35
+ 'PluginInitializationError',
36
+ 'Plugin manager initialization failed',
37
+ ) {}
38
+
39
+ /**
40
+ * Identifier denoting a Manager.
41
+ */
42
+ export const ManagerTypeId: unique symbol = Symbol.for('@dxos/app-framework/Manager');
43
+ export type ManagerTypeId = typeof ManagerTypeId;
44
+
45
+ export type ManagerOptions = {
46
+ pluginLoader: (id: string) => Effect.Effect<Plugin.Plugin, Error>;
47
+ plugins?: Plugin.Plugin[];
48
+ core?: string[];
49
+ enabled?: string[];
50
+ registry?: Registry.Registry;
51
+ /**
52
+ * Hook called when a plugin is removed via {@link PluginManager.remove}. Used by the
53
+ * host app to clean up persisted state (e.g. evict offline-cached plugin assets).
54
+ * Failures are logged and swallowed; removal still succeeds even if the hook fails.
55
+ */
56
+ onRemove?: (id: string) => Effect.Effect<void, unknown>;
57
+ };
58
+
59
+ export type ActivationMessage = {
60
+ event: string;
61
+ state: 'activating' | 'activated' | 'error';
62
+ /** Module ID when the message pertains to a specific module activation. */
63
+ module?: string;
64
+ error?: Error;
65
+ };
66
+
67
+ /**
68
+ * Interface for the Plugin Manager.
69
+ */
70
+ export interface PluginManager {
71
+ readonly [ManagerTypeId]: ManagerTypeId;
72
+ readonly activation: PubSub.PubSub<ActivationMessage>;
73
+ readonly capabilities: CapabilityManager.CapabilityManager;
74
+ readonly registry: Registry.Registry;
75
+
76
+ readonly plugins: Atom.Atom<readonly Plugin.Plugin[]>;
77
+ readonly core: Atom.Atom<readonly string[]>;
78
+ readonly enabled: Atom.Atom<readonly string[]>;
79
+ readonly modules: Atom.Atom<readonly Plugin.PluginModule[]>;
80
+ readonly active: Atom.Atom<readonly string[]>;
81
+ readonly eventsFired: Atom.Atom<readonly string[]>;
82
+ readonly pendingReset: Atom.Atom<readonly string[]>;
83
+
84
+ getPlugins(): readonly Plugin.Plugin[];
85
+ getCore(): readonly string[];
86
+ getEnabled(): readonly string[];
87
+ getModules(): readonly Plugin.PluginModule[];
88
+ getActive(): readonly string[];
89
+ getEventsFired(): readonly string[];
90
+ getPendingReset(): readonly string[];
91
+
92
+ /**
93
+ * Loads a plugin via the plugin loader and registers it without enabling it.
94
+ * Returns the loaded plugin so callers can enable it by its canonical id
95
+ * (which may differ from the locator used to load it, e.g. URL loaders).
96
+ */
97
+ add(id: string): Effect.Effect<Plugin.Plugin, Error>;
98
+ enable(id: string): Effect.Effect<boolean, Error>;
99
+ remove(id: string): Effect.Effect<boolean, Error>;
100
+ disable(id: string): Effect.Effect<boolean, Error>;
101
+ // TODO(wittjosiah): Improve error typing.
102
+ activate(
103
+ event: ActivationEvent.ActivationEvent | string,
104
+ params?: { before?: string; after?: string },
105
+ ): Effect.Effect<boolean, Error>;
106
+ deactivate(id: string): Effect.Effect<boolean, Error>;
107
+ reset(event: ActivationEvent.ActivationEvent | string): Effect.Effect<boolean, Error>;
108
+
109
+ /**
110
+ * Shuts down the manager by deactivating all active modules in reverse activation order,
111
+ * clearing all capabilities, and resetting lifecycle bookkeeping.
112
+ * Plugins, core, enabled, and modules remain intact so the manager can be reused.
113
+ */
114
+ shutdown(): Effect.Effect<boolean, Error>;
115
+ }
116
+
117
+ /**
118
+ * Type guard to check if a value is a PluginManager.
119
+ */
120
+ export const isManager = (value: unknown): value is PluginManager => {
121
+ return typeof value === 'object' && value !== null && ManagerTypeId in value;
122
+ };
123
+
124
+ /**
125
+ * Internal implementation of PluginManager.
126
+ */
127
+ class ManagerImpl implements PluginManager {
128
+ readonly [ManagerTypeId]: ManagerTypeId = ManagerTypeId;
129
+ readonly activation = Effect.runSync(PubSub.unbounded<ActivationMessage>());
130
+ readonly capabilities: CapabilityManager.CapabilityManager;
131
+ readonly registry: Registry.Registry;
132
+
133
+ private readonly _pluginsAtom: Atom.Writable<Plugin.Plugin[]>;
134
+ private readonly _coreAtom: Atom.Writable<string[]>;
135
+ private readonly _enabledAtom: Atom.Writable<string[]>;
136
+ private readonly _modulesAtom: Atom.Writable<Plugin.PluginModule[]>;
137
+ private readonly _activeAtom: Atom.Writable<string[]>;
138
+ private readonly _eventsFiredAtom: Atom.Writable<string[]>;
139
+ private readonly _pendingResetAtom: Atom.Writable<string[]>;
140
+ private readonly _pluginLoader: ManagerOptions['pluginLoader'];
141
+ private readonly _onRemove: ManagerOptions['onRemove'];
142
+ private readonly _capabilities = new Map<string, Capability.Any[]>();
143
+ private readonly _moduleMemoMap = new Map<Plugin.PluginModule['id'], Deferred.Deferred<Capability.Any[], Error>>();
144
+ private readonly _moduleSemaphores = new Map<Plugin.PluginModule['id'], Effect.Semaphore>();
145
+ // Coalesces concurrent `_resolveLazyPlugin` calls per plugin id. Without
146
+ // this, two callers entering `enable(id)` before the swap completes would
147
+ // each invoke `mod.default(options)` and produce distinct module objects,
148
+ // defeating `_addModule`'s reference-equality dedupe and racing the
149
+ // `_pluginsAtom` swap.
150
+ private readonly _resolvingPlugins = new Map<string, Deferred.Deferred<Plugin.Plugin, Plugin.LazyPluginError>>();
151
+ private readonly _activatingEvents = Effect.runSync(Ref.make<string[]>([]));
152
+ private readonly _activatingModules = Effect.runSync(Ref.make<string[]>([]));
153
+ private readonly _inFlightFibers = Effect.runSync(Ref.make<Array<Fiber.Fiber<unknown, unknown>>>([]));
154
+ private readonly _shutdownSemaphore = Effect.runSync(Effect.makeSemaphore(1));
155
+ private readonly _shuttingDown = Effect.runSync(Ref.make(false));
156
+ // Tracks the constructor-launched core/enabled `enable()` calls so that
157
+ // `activate` can wait for module registration before dispatching events.
158
+ // Lazy plugins make `enable` asynchronous (a dynamic `import()` happens
159
+ // inside it), so without this synchronization an `activate` triggered
160
+ // immediately after `make` could fire on an empty module set. Failures
161
+ // are wrapped in `PluginInitializationError` so awaiters get a tagged
162
+ // error rather than the wide `Error` produced by the underlying chain.
163
+ private readonly _initialization = Effect.runSync(Deferred.make<void, PluginInitializationError>());
164
+
165
+ constructor({
166
+ pluginLoader,
167
+ plugins = [],
168
+ core = plugins.map(({ meta }) => meta.id),
169
+ enabled = [],
170
+ registry,
171
+ onRemove,
172
+ }: ManagerOptions) {
173
+ this.registry = registry ?? Registry.make();
174
+ this.capabilities = CapabilityManager.make({
175
+ registry: this.registry,
176
+ });
177
+
178
+ this._pluginLoader = pluginLoader;
179
+ this._onRemove = onRemove;
180
+ this._pluginsAtom = Atom.make(plugins).pipe(Atom.keepAlive);
181
+ this._coreAtom = Atom.make(core).pipe(Atom.keepAlive);
182
+ this._enabledAtom = Atom.make(enabled).pipe(Atom.keepAlive);
183
+ this._modulesAtom = Atom.make<Plugin.PluginModule[]>([]).pipe(Atom.keepAlive);
184
+ this._activeAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
185
+ this._eventsFiredAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
186
+ this._pendingResetAtom = Atom.make<string[]>([]).pipe(Atom.keepAlive);
187
+ plugins.forEach((plugin) => this._addPlugin(plugin));
188
+ // Dedupe before mapping to `enable` — `core` and `enabled` may overlap (an
189
+ // app-supplied plugin can be in both), and concurrent `enable(id)` calls
190
+ // for the same id are not idempotent (each would re-run the lazy resolve
191
+ // and double-register modules). `new Set([...])` preserves first-seen
192
+ // order which matches the natural core-before-enabled precedence.
193
+ const initialIds = [...new Set([...core, ...enabled])];
194
+ void Effect.all(initialIds.map((id) => this.enable(id)))
195
+ .pipe(
196
+ Effect.mapError((cause) => new PluginInitializationError({ cause })),
197
+ Effect.tap(() => Deferred.succeed(this._initialization, undefined)),
198
+ Effect.tapErrorCause((cause) => Deferred.failCause(this._initialization, cause)),
199
+ )
200
+ .pipe(runAndForwardErrors);
201
+ }
202
+
203
+ get plugins(): Atom.Atom<readonly Plugin.Plugin[]> {
204
+ return this._pluginsAtom;
205
+ }
206
+
207
+ get core(): Atom.Atom<readonly string[]> {
208
+ return this._coreAtom;
209
+ }
210
+
211
+ /**
212
+ * Ids of plugins that are currently enabled.
213
+ */
214
+ get enabled(): Atom.Atom<readonly string[]> {
215
+ return this._enabledAtom;
216
+ }
217
+
218
+ /**
219
+ * Modules of plugins which are currently enabled.
220
+ */
221
+ get modules(): Atom.Atom<readonly Plugin.PluginModule[]> {
222
+ return this._modulesAtom;
223
+ }
224
+
225
+ /**
226
+ * Ids of modules which are currently active.
227
+ */
228
+ get active(): Atom.Atom<readonly string[]> {
229
+ return this._activeAtom;
230
+ }
231
+
232
+ /**
233
+ * Ids of events which have been fired.
234
+ */
235
+ get eventsFired(): Atom.Atom<readonly string[]> {
236
+ return this._eventsFiredAtom;
237
+ }
238
+
239
+ /**
240
+ * Ids of modules which are pending reset.
241
+ */
242
+ get pendingReset(): Atom.Atom<readonly string[]> {
243
+ return this._pendingResetAtom;
244
+ }
245
+
246
+ getPlugins(): readonly Plugin.Plugin[] {
247
+ return this._get(this._pluginsAtom);
248
+ }
249
+
250
+ getCore(): readonly string[] {
251
+ return this._get(this._coreAtom);
252
+ }
253
+
254
+ getEnabled(): readonly string[] {
255
+ return this._get(this._enabledAtom);
256
+ }
257
+
258
+ getModules(): readonly Plugin.PluginModule[] {
259
+ return this._get(this._modulesAtom);
260
+ }
261
+
262
+ getActive(): readonly string[] {
263
+ return this._get(this._activeAtom);
264
+ }
265
+
266
+ getEventsFired(): readonly string[] {
267
+ return this._get(this._eventsFiredAtom);
268
+ }
269
+
270
+ getPendingReset(): readonly string[] {
271
+ return this._get(this._pendingResetAtom);
272
+ }
273
+
274
+ /**
275
+ * Adds a plugin to the manager via the plugin loader.
276
+ * The plugin is registered but not enabled; call `enable` separately to activate it.
277
+ * @param id The id of the plugin.
278
+ */
279
+ add(id: string): Effect.Effect<Plugin.Plugin, Error> {
280
+ return Effect.gen(this, function* () {
281
+ log('add plugin', { id });
282
+ const plugin = yield* this._pluginLoader(id);
283
+ this._addPlugin(plugin);
284
+ return plugin;
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Enables a plugin.
290
+ * @param id The id of the plugin.
291
+ */
292
+ enable(id: string): Effect.Effect<boolean, Error> {
293
+ return Effect.gen(this, function* () {
294
+ log('enable plugin', { id });
295
+ const stub = this._getPlugin(id);
296
+ if (!stub) {
297
+ return false;
298
+ }
299
+
300
+ const plugin = yield* this._resolveLazyPlugin(stub);
301
+
302
+ this._update(this._enabledAtom, (enabled) => (enabled.includes(id) ? enabled : [...enabled, id]));
303
+
304
+ plugin.modules.forEach((module) => {
305
+ this._addModule(module);
306
+ this._setPendingResetByModule(module);
307
+ });
308
+
309
+ log('pending reset', { events: [...this.getPendingReset()] });
310
+ yield* Effect.all(
311
+ this.getPendingReset().map((event) => this.activate(event)),
312
+ { concurrency: 'unbounded' },
313
+ );
314
+
315
+ return true;
316
+ });
317
+ }
318
+
319
+ /**
320
+ * Resolves a lazy plugin stub (returned by {@link Plugin.lazy}) to its
321
+ * loaded form and swaps it into `_pluginsAtom`. Returns the input unchanged
322
+ * when the plugin is already resolved, so callers can `yield*` this
323
+ * unconditionally. The lazy stub carries `meta` synchronously but its
324
+ * `modules` list is empty until the loader resolves; the swap ensures
325
+ * subsequent enable/disable operations see the resolved plugin.
326
+ *
327
+ * Concurrent calls for the same id are coalesced via `_resolvingPlugins`:
328
+ * the first caller starts the resolution, every subsequent caller awaits
329
+ * the same `Deferred`. On failure we publish a `lazy:<id>` error message
330
+ * and skip the atom swap so the failure is observable to the activation
331
+ * subscriber and a retry can be attempted.
332
+ */
333
+ private _resolveLazyPlugin(plugin: Plugin.Plugin): Effect.Effect<Plugin.Plugin, Plugin.LazyPluginError> {
334
+ return Effect.gen(this, function* () {
335
+ if (!Plugin.isLazy(plugin)) {
336
+ return plugin;
337
+ }
338
+ const id = plugin.meta.id;
339
+
340
+ const existing = this._resolvingPlugins.get(id);
341
+ if (existing) {
342
+ return yield* Deferred.await(existing);
343
+ }
344
+ const deferred = yield* Deferred.make<Plugin.Plugin, Plugin.LazyPluginError>();
345
+ this._resolvingPlugins.set(id, deferred);
346
+
347
+ return yield* Effect.gen(this, function* () {
348
+ log('resolving lazy plugin', { id });
349
+ yield* PubSub.publish(this.activation, { event: '', state: 'activating', module: `lazy:${id}` });
350
+ const resolvedPlugin = yield* Plugin.resolveLazy(plugin);
351
+ this._update(this._pluginsAtom, (plugins) => plugins.map((p) => (p.meta.id === id ? resolvedPlugin : p)));
352
+ yield* PubSub.publish(this.activation, { event: '', state: 'activated', module: `lazy:${id}` });
353
+ return resolvedPlugin;
354
+ }).pipe(
355
+ Effect.tapError((error) =>
356
+ PubSub.publish(this.activation, { event: '', state: 'error', module: `lazy:${id}`, error }),
357
+ ),
358
+ Effect.tap((value) => Deferred.succeed(deferred, value)),
359
+ Effect.tapErrorCause((cause) => Deferred.failCause(deferred, cause)),
360
+ Effect.ensuring(Effect.sync(() => this._resolvingPlugins.delete(id))),
361
+ );
362
+ });
363
+ }
364
+
365
+ /**
366
+ * Removes a plugin from the manager.
367
+ * @param id The id of the plugin.
368
+ */
369
+ remove(id: string): Effect.Effect<boolean, Error> {
370
+ return Effect.gen(this, function* () {
371
+ log('remove plugin', { id });
372
+ const disabled = yield* this.disable(id);
373
+ if (!disabled) {
374
+ return false;
375
+ }
376
+
377
+ this._removePlugin(id);
378
+ if (this._onRemove) {
379
+ this._runForkedFiber(
380
+ this._onRemove(id).pipe(
381
+ Effect.tapError((error) => Effect.sync(() => log.warn('plugin remove hook failed', { id, error }))),
382
+ Effect.ignore,
383
+ ),
384
+ );
385
+ }
386
+ return true;
387
+ });
388
+ }
389
+
390
+ /**
391
+ * Disables a plugin.
392
+ * @param id The id of the plugin.
393
+ */
394
+ disable(id: string): Effect.Effect<boolean, Error> {
395
+ return Effect.gen(this, function* () {
396
+ log('disable plugin', { id });
397
+ if (this._get(this._coreAtom).includes(id)) {
398
+ return false;
399
+ }
400
+
401
+ const plugin = this._getPlugin(id);
402
+ if (!plugin) {
403
+ return false;
404
+ }
405
+
406
+ const enabledIndex = this._get(this._enabledAtom).findIndex((enabled) => enabled === id);
407
+ if (enabledIndex !== -1) {
408
+ this._update(this._enabledAtom, (enabled) => enabled.filter((item) => item !== id));
409
+ yield* this.deactivate(id);
410
+
411
+ plugin.modules.forEach((module) => {
412
+ this._removeModule(module.id);
413
+ });
414
+ }
415
+
416
+ return true;
417
+ });
418
+ }
419
+
420
+ /**
421
+ * Activates plugins based on the activation event.
422
+ * @param event The activation event.
423
+ * @returns Whether the activation was successful.
424
+ */
425
+ activate(
426
+ event: ActivationEvent.ActivationEvent | string,
427
+ params?: { before?: string; after?: string },
428
+ ): Effect.Effect<boolean, Error> {
429
+ const key = typeof event === 'string' ? event : ActivationEvent.eventKey(event);
430
+ return Effect.gen(this, function* () {
431
+ if (yield* this._isShuttingDown()) {
432
+ log('skipping activation during shutdown', { key, ...params });
433
+ return false;
434
+ }
435
+
436
+ // Wait for the constructor's core/enabled `enable()` chain — including
437
+ // any async dynamic imports for lazy plugins — to finish registering
438
+ // modules. Without this, dispatching to an empty module set is the
439
+ // observable symptom of the race.
440
+ yield* Deferred.await(this._initialization);
441
+
442
+ return yield* Effect.withFiberRuntime<boolean, Error>((fiber) =>
443
+ this._activateEvent(key, params, fiber).pipe(
444
+ together(
445
+ Effect.sleep(Duration.seconds(15)).pipe(
446
+ Effect.andThen(Effect.sync(() => log.warn('event activation is taking a long time', { event: key }))),
447
+ ),
448
+ ),
449
+ Performance.addTrackEntry({
450
+ name: typeof event === 'string' ? event : ActivationEvent.eventKey(event),
451
+ devtools: {
452
+ dataType: 'track-entry',
453
+ track: 'Event Activation',
454
+ trackGroup: 'Composer',
455
+ color: 'primary',
456
+ },
457
+ }),
458
+ ),
459
+ );
460
+ });
461
+ }
462
+
463
+ /**
464
+ * Deactivates all of the modules for a plugin.
465
+ * @param id The id of the plugin.
466
+ * @returns Whether the deactivation was successful.
467
+ */
468
+ deactivate(id: string): Effect.Effect<boolean, Error> {
469
+ return Effect.gen(this, function* () {
470
+ const plugin = this._getPlugin(id);
471
+ if (!plugin) {
472
+ return false;
473
+ }
474
+
475
+ const modules = plugin.modules;
476
+ const results = yield* Effect.all(
477
+ modules.map((module) => this._deactivateModule(module)),
478
+ { concurrency: 'unbounded' },
479
+ );
480
+ return results.every((result) => result);
481
+ });
482
+ }
483
+
484
+ /**
485
+ * Re-activates the modules that were activated by the event.
486
+ * @param event The activation event.
487
+ * @returns Whether the reset was successful.
488
+ */
489
+ reset(event: ActivationEvent.ActivationEvent | string): Effect.Effect<boolean, Error> {
490
+ return Effect.gen(this, function* () {
491
+ const key = typeof event === 'string' ? event : ActivationEvent.eventKey(event);
492
+ log('reset', { key });
493
+ const modules = this._getActiveModulesByEvent(key);
494
+ const results = yield* Effect.all(
495
+ modules.map((module) => this._deactivateModule(module)),
496
+ { concurrency: 'unbounded' },
497
+ );
498
+
499
+ if (results.every((result) => result)) {
500
+ return yield* this.activate(key);
501
+ } else {
502
+ return false;
503
+ }
504
+ });
505
+ }
506
+
507
+ shutdown(): Effect.Effect<boolean, Error> {
508
+ return this._shutdownSemaphore.withPermits(1)(
509
+ Effect.gen(this, function* () {
510
+ yield* Ref.set(this._shuttingDown, true);
511
+ log('shutdown');
512
+
513
+ yield* this._interruptInFlightActivations();
514
+
515
+ const activeIds = [...this._get(this._activeAtom)].reverse();
516
+ const allModules = this._get(this._modulesAtom);
517
+ const modulesToDeactivate = activeIds
518
+ .map((id) => allModules.find((module) => module.id === id))
519
+ .filter((module): module is Plugin.PluginModule => module != null);
520
+
521
+ for (const module of modulesToDeactivate) {
522
+ yield* this._deactivateModule(module);
523
+ }
524
+
525
+ this._set(this._eventsFiredAtom, []);
526
+ this._set(this._pendingResetAtom, []);
527
+ this._moduleMemoMap.clear();
528
+ yield* Ref.set(this._activatingEvents, []);
529
+ yield* Ref.set(this._activatingModules, []);
530
+
531
+ log('shutdown complete');
532
+ return true;
533
+ }).pipe(Effect.ensuring(Ref.set(this._shuttingDown, false))),
534
+ );
535
+ }
536
+
537
+ //
538
+ // State helpers
539
+ //
540
+
541
+ private _get<T>(atom: Atom.Atom<T>): T {
542
+ return this.registry.get(atom);
543
+ }
544
+
545
+ private _set<T>(atom: Atom.Writable<T>, value: T): void {
546
+ this.registry.set(atom, value);
547
+ }
548
+
549
+ private _update<T>(atom: Atom.Writable<T>, updater: (current: T) => T): void {
550
+ this._set(atom, updater(this._get(atom)));
551
+ }
552
+
553
+ private _isShuttingDown(): Effect.Effect<boolean> {
554
+ return Ref.get(this._shuttingDown);
555
+ }
556
+
557
+ private _getPlugin(id: string): Plugin.Plugin | undefined {
558
+ return this._get(this._pluginsAtom).find((plugin) => plugin.meta.id === id);
559
+ }
560
+
561
+ private _getActiveModules(): Plugin.PluginModule[] {
562
+ const active = this._get(this._activeAtom);
563
+ return this._get(this._modulesAtom).filter((module) => active.includes(module.id));
564
+ }
565
+
566
+ private _getInactiveModules(): Plugin.PluginModule[] {
567
+ const active = this._get(this._activeAtom);
568
+ return this._get(this._modulesAtom).filter((module) => !active.includes(module.id));
569
+ }
570
+
571
+ private _getActiveModulesByEvent(key: string): Plugin.PluginModule[] {
572
+ return this._getActiveModules().filter((module) =>
573
+ ActivationEvent.getEvents(module.activatesOn).map(ActivationEvent.eventKey).includes(key),
574
+ );
575
+ }
576
+
577
+ private _getInactiveModulesByEvent(key: string): Plugin.PluginModule[] {
578
+ return this._getInactiveModules().filter((module) =>
579
+ ActivationEvent.getEvents(module.activatesOn).map(ActivationEvent.eventKey).includes(key),
580
+ );
581
+ }
582
+
583
+ private _setPendingResetByModule(module: Plugin.PluginModule): void {
584
+ const activationEvents = ActivationEvent.getEvents(module.activatesOn)
585
+ .map(ActivationEvent.eventKey)
586
+ .filter((key) => this._get(this._eventsFiredAtom).includes(key));
587
+
588
+ const pendingReset = Array.fromIterable(new Set(activationEvents)).filter((event) => {
589
+ const pending = this._get(this._pendingResetAtom);
590
+ return !pending.includes(event);
591
+ });
592
+ if (pendingReset.length > 0) {
593
+ log('pending reset', { events: pendingReset });
594
+ this._update(this._pendingResetAtom, (current) => [...current, ...pendingReset]);
595
+ }
596
+ }
597
+
598
+ private _clearPendingReset(key: string): void {
599
+ const pendingIndex = this._get(this._pendingResetAtom).findIndex((event) => event === key);
600
+ if (pendingIndex !== -1) {
601
+ this._update(this._pendingResetAtom, (pending) => pending.filter((event) => event !== key));
602
+ }
603
+ }
604
+
605
+ //
606
+ // Fiber helpers
607
+ //
608
+
609
+ private _interruptInFlightActivations(): Effect.Effect<void> {
610
+ return Effect.gen(this, function* () {
611
+ const inFlightFibers = yield* Ref.get(this._inFlightFibers);
612
+ yield* Effect.forEach(inFlightFibers, (fiber) => Fiber.interrupt(fiber), {
613
+ concurrency: 'unbounded',
614
+ });
615
+ });
616
+ }
617
+
618
+ private _trackFiber(
619
+ ref: Ref.Ref<Array<Fiber.Fiber<unknown, unknown>>>,
620
+ fiber: Fiber.Fiber<unknown, unknown>,
621
+ ): Effect.Effect<void> {
622
+ return Ref.update(ref, (fibers) => [...fibers, fiber]);
623
+ }
624
+
625
+ private _untrackFiber(
626
+ ref: Ref.Ref<Array<Fiber.Fiber<unknown, unknown>>>,
627
+ fiber: Fiber.Fiber<unknown, unknown>,
628
+ ): Effect.Effect<void> {
629
+ return Ref.update(ref, (fibers) => fibers.filter((trackedFiber) => trackedFiber !== fiber));
630
+ }
631
+
632
+ /**
633
+ * Spawns an effect on the default runtime and registers the resulting fiber in
634
+ * `_inFlightFibers` so {@link shutdown} can interrupt it. Used from sync entry
635
+ * points like {@link remove} where there is no enclosing Effect to fork from;
636
+ * inside an Effect chain prefer the existing track/await/untrack pattern.
637
+ */
638
+ private _runForkedFiber<E>(effect: Effect.Effect<void, E>): void {
639
+ const fiber = Effect.runFork(effect);
640
+ Effect.runSync(this._trackFiber(this._inFlightFibers, fiber));
641
+ Effect.runFork(Fiber.await(fiber).pipe(Effect.andThen(() => this._untrackFiber(this._inFlightFibers, fiber))));
642
+ }
643
+
644
+ //
645
+ // Registration helpers
646
+ //
647
+
648
+ private _addPlugin(plugin: Plugin.Plugin): void {
649
+ log('add plugin', { id: plugin.meta.id });
650
+ // TODO(wittjosiah): Find a way to add a warning for duplicate plugins that doesn't cause log spam.
651
+ this._update(this._pluginsAtom, (plugins) => (plugins.includes(plugin) ? plugins : [...plugins, plugin]));
652
+ }
653
+
654
+ private _removePlugin(id: string): void {
655
+ log('remove plugin', { id });
656
+ this._update(this._pluginsAtom, (plugins) => plugins.filter((plugin) => plugin.meta.id !== id));
657
+ }
658
+
659
+ private _addModule(module: Plugin.PluginModule): void {
660
+ log('add module', { id: module.id });
661
+ // TODO(wittjosiah): Find a way to add a warning for duplicate modules that doesn't cause log spam.
662
+ this._update(this._modulesAtom, (modules) => (modules.includes(module) ? modules : [...modules, module]));
663
+ }
664
+
665
+ private _removeModule(id: string): void {
666
+ log('remove module', { id });
667
+ this._update(this._modulesAtom, (modules) => modules.filter((module) => module.id !== id));
668
+ }
669
+
670
+ //
671
+ // Activation helpers
672
+ //
673
+
674
+ private _activateEvent(
675
+ key: string,
676
+ params: { before?: string; after?: string } | undefined,
677
+ fiber: Fiber.Fiber<unknown, unknown>,
678
+ ): Effect.Effect<boolean, Error> {
679
+ return Effect.gen(this, function* () {
680
+ yield* this._trackFiber(this._inFlightFibers, fiber);
681
+ log('activating', { key, ...params });
682
+ yield* Ref.update(this._activatingEvents, (activating) => Array.append(activating, key));
683
+ this._clearPendingReset(key);
684
+
685
+ const activatingEvents = yield* this._activatingEvents;
686
+ const activatingModules = yield* this._activatingModules;
687
+ const modules = this._getModulesForActivation(key, activatingEvents, activatingModules);
688
+ if (modules.length === 0) {
689
+ log('no modules to activate', { key });
690
+ if (!this._get(this._eventsFiredAtom).includes(key)) {
691
+ this._update(this._eventsFiredAtom, (events) => [...events, key]);
692
+ }
693
+ return false;
694
+ }
695
+
696
+ return yield* this._activateModulesForEvent(key, modules, activatingEvents);
697
+ }).pipe(
698
+ Effect.ensuring(
699
+ Effect.all([
700
+ this._untrackFiber(this._inFlightFibers, fiber),
701
+ Ref.update(this._activatingEvents, (activating) => Array.filter(activating, (event) => event !== key)),
702
+ ]),
703
+ ),
704
+ );
705
+ }
706
+
707
+ private _activateModulesForEvent(
708
+ key: string,
709
+ modules: Plugin.PluginModule[],
710
+ activatingEvents: string[],
711
+ ): Effect.Effect<boolean, Error> {
712
+ const activatingModuleIds = modules.map((module) => module.id);
713
+ return Effect.gen(this, function* () {
714
+ yield* Ref.update(this._activatingModules, (activating) => Array.appendAll(activating, activatingModuleIds));
715
+
716
+ log('activating modules', { key, modules: activatingModuleIds });
717
+ performance.mark(`event:${key}:start`);
718
+ yield* PubSub.publish(this.activation, { event: key, state: 'activating' });
719
+
720
+ yield* this._activateRelatedEvents(key, this._getBeforeEvents(modules, activatingEvents), 'before');
721
+
722
+ const capabilities = yield* this._loadCapabilitiesForModules(key, modules);
723
+ yield* this._contributeCapabilitiesForModules(modules, capabilities);
724
+
725
+ yield* this._activateRelatedEvents(key, this._getAfterEvents(modules, activatingEvents), 'after');
726
+
727
+ if (!this._get(this._eventsFiredAtom).includes(key)) {
728
+ this._update(this._eventsFiredAtom, (events) => [...events, key]);
729
+ }
730
+
731
+ performance.mark(`event:${key}:end`);
732
+ performance.measure(`event:${key}`, `event:${key}:start`, `event:${key}:end`);
733
+ yield* PubSub.publish(this.activation, { event: key, state: 'activated' });
734
+ log('activated', { key });
735
+
736
+ return true;
737
+ }).pipe(
738
+ Effect.ensuring(
739
+ Ref.update(this._activatingModules, (activating) =>
740
+ Array.filter(activating, (module) => !activatingModuleIds.includes(module)),
741
+ ),
742
+ ),
743
+ );
744
+ }
745
+
746
+ private _getModulesForActivation(
747
+ key: string,
748
+ activatingEvents: string[],
749
+ activatingModules: string[],
750
+ ): Plugin.PluginModule[] {
751
+ return this._getInactiveModulesByEvent(key).filter((module) => {
752
+ const allOf = ActivationEvent.isAllOf(module.activatesOn);
753
+ if (!allOf) {
754
+ return true;
755
+ }
756
+
757
+ // Check to see if all of the events in the `allOf` have been fired.
758
+ // An event can be considered "fired" if it is in the `eventsFired` list or if it is currently being activated.
759
+ const events = ActivationEvent.getEvents(module.activatesOn).filter(
760
+ (event) => ActivationEvent.eventKey(event) !== key,
761
+ );
762
+ return (
763
+ events.every(
764
+ (event) =>
765
+ this._get(this._eventsFiredAtom).includes(ActivationEvent.eventKey(event)) ||
766
+ activatingEvents.includes(ActivationEvent.eventKey(event)),
767
+ ) && !activatingModules.includes(module.id)
768
+ );
769
+ });
770
+ }
771
+
772
+ private _getBeforeEvents(
773
+ modules: Plugin.PluginModule[],
774
+ activatingEvents: string[],
775
+ ): ActivationEvent.ActivationEvent[] {
776
+ return Function.pipe(
777
+ modules,
778
+ Array.flatMap((module) => module.firesBeforeActivation ?? []),
779
+ HashSet.fromIterable,
780
+ HashSet.toValues,
781
+ Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
782
+ );
783
+ }
784
+
785
+ private _getAfterEvents(
786
+ modules: Plugin.PluginModule[],
787
+ activatingEvents: string[],
788
+ ): ActivationEvent.ActivationEvent[] {
789
+ return Function.pipe(
790
+ modules,
791
+ Array.flatMap((module) => module.firesAfterActivation ?? []),
792
+ HashSet.fromIterable,
793
+ HashSet.toValues,
794
+ Array.filter((event) => !activatingEvents.includes(ActivationEvent.eventKey(event))),
795
+ );
796
+ }
797
+
798
+ private _activateRelatedEvents(
799
+ key: string,
800
+ events: ActivationEvent.ActivationEvent[],
801
+ phase: 'before' | 'after',
802
+ ): Effect.Effect<void, Error> {
803
+ const logLabel = phase === 'before' ? 'firesBeforeActivation' : 'firesAfterActivation';
804
+ const eventKey = phase === 'before' ? 'beforeEvents' : 'afterEvents';
805
+ return Function.pipe(
806
+ events,
807
+ Array.map((event) => this.activate(event, phase === 'before' ? { before: key } : { after: key })),
808
+ Effect.allWith({ concurrency: 'unbounded' }),
809
+ together(
810
+ Effect.sleep(Duration.seconds(10)).pipe(
811
+ Effect.andThen(
812
+ Effect.sync(() =>
813
+ log.warn(`${logLabel} is taking a long time`, {
814
+ event: key,
815
+ [eventKey]: events.map(ActivationEvent.eventKey),
816
+ }),
817
+ ),
818
+ ),
819
+ ),
820
+ ),
821
+ Effect.asVoid,
822
+ );
823
+ }
824
+
825
+ //
826
+ // Module lifecycle helpers
827
+ //
828
+
829
+ private _loadCapabilitiesForModules(
830
+ key: string,
831
+ modules: Plugin.PluginModule[],
832
+ ): Effect.Effect<Capability.Any[][], Error> {
833
+ return Function.pipe(
834
+ modules,
835
+ Array.map((mod) => this._loadModule(mod, key)),
836
+ Effect.allWith({ concurrency: 'unbounded' }),
837
+ Effect.catchAll((error) => {
838
+ return Effect.gen(this, function* () {
839
+ yield* PubSub.publish(this.activation, { event: key, state: 'error', error });
840
+ return yield* Effect.fail(error);
841
+ });
842
+ }),
843
+ );
844
+ }
845
+
846
+ private _contributeCapabilitiesForModules(
847
+ modules: Plugin.PluginModule[],
848
+ capabilities: Capability.Any[][],
849
+ ): Effect.Effect<void, Error> {
850
+ return Function.pipe(
851
+ modules,
852
+ Array.zip(capabilities),
853
+ Array.map(([module, capabilitySet]) => this._contributeCapabilities(module, capabilitySet)),
854
+ // TODO(wittjosiah): This currently can't be run in parallel, and inserting
855
+ // any yield between contributions (`Effect.yieldNow()`, `Effect.sleep(0)`)
856
+ // races the `allOf` activation-event resolver — observed as a System
857
+ // Error dialog on warm reloads. Contributions must stay strictly
858
+ // synchronous within an event; React paint slots have to be found at
859
+ // event boundaries higher up the call chain.
860
+ Effect.all,
861
+ Effect.asVoid,
862
+ );
863
+ }
864
+
865
+ private _getModuleSemaphore(moduleId: Plugin.PluginModule['id']): Effect.Semaphore {
866
+ let semaphore = this._moduleSemaphores.get(moduleId);
867
+ if (!semaphore) {
868
+ semaphore = Effect.runSync(Effect.makeSemaphore(1));
869
+ this._moduleSemaphores.set(moduleId, semaphore);
870
+ }
871
+ return semaphore;
872
+ }
873
+
874
+ // `parentEvent` is the activation event that first triggered this module
875
+ // load — included in `activating`/`activated` PubSub messages so subscribers
876
+ // (e.g. the boot loader's status listener) can associate a module with its
877
+ // triggering event in the trace. The same module may be referenced by
878
+ // multiple events, but module loads are memoized via `_moduleMemoMap`, so
879
+ // only the first event to need it will appear here; later events await the
880
+ // cached deferred without re-publishing.
881
+ private _loadModule = (module: Plugin.PluginModule, parentEvent: string): Effect.Effect<Capability.Any[], Error> =>
882
+ Effect.gen(this, function* () {
883
+ const semaphore = this._getModuleSemaphore(module.id);
884
+
885
+ // Atomically check-and-set under per-module semaphore to prevent race conditions.
886
+ const deferredToAwait = yield* Effect.gen(this, function* () {
887
+ const existing = this._moduleMemoMap.get(module.id);
888
+ if (existing) {
889
+ return existing;
890
+ }
891
+
892
+ // First caller - create deferred, store it, and start loading in background.
893
+ const deferred = yield* Deferred.make<Capability.Any[], Error>();
894
+ this._moduleMemoMap.set(module.id, deferred);
895
+
896
+ const loadEffect = Effect.gen(this, function* () {
897
+ log('loading module', { module: module.id, parentEvent });
898
+ performance.mark(`module:${module.id}:start`);
899
+ yield* PubSub.publish(this.activation, { event: parentEvent, state: 'activating', module: module.id });
900
+ const [duration, capabilities] = yield* module
901
+ .activate()
902
+ .pipe(
903
+ Effect.provideService(Capability.Service, this.capabilities),
904
+ Effect.provideService(Plugin.Service, this),
905
+ Effect.timed,
906
+ );
907
+ const normalized = capabilities == null ? [] : Array.isArray(capabilities) ? capabilities : [capabilities];
908
+ const elapsed = Duration.toMillis(duration);
909
+ performance.mark(`module:${module.id}:end`);
910
+ performance.measure(`module:${module.id}`, `module:${module.id}:start`, `module:${module.id}:end`);
911
+ yield* PubSub.publish(this.activation, { event: parentEvent, state: 'activated', module: module.id });
912
+ log('loaded module', {
913
+ module: module.id,
914
+ parentEvent,
915
+ elapsed,
916
+ failed: false,
917
+ });
918
+ return normalized as Capability.Any[];
919
+ }).pipe(
920
+ Effect.withSpan('PluginManager._loadModule'),
921
+ together(
922
+ Effect.sleep(Duration.seconds(10)).pipe(
923
+ Effect.andThen(
924
+ Effect.sync(() => log.warn(`module is taking a long time to activate`, { module: module.id })),
925
+ ),
926
+ ),
927
+ ),
928
+ Performance.addTrackEntry({
929
+ name: module.id,
930
+ devtools: {
931
+ dataType: 'track-entry',
932
+ track: 'Module Activation',
933
+ trackGroup: 'Composer',
934
+ color: 'primary',
935
+ },
936
+ }),
937
+ );
938
+
939
+ // Fork the load to run in background, completing the deferred when done.
940
+ const fiber = yield* Effect.forkDaemon(
941
+ loadEffect.pipe(
942
+ Effect.tap((result) => Deferred.succeed(deferred, result)),
943
+ Effect.catchAllCause((cause) => {
944
+ const error = Cause.squash(cause);
945
+ log.error('module failed to activate', {
946
+ module: module.id,
947
+ error: error instanceof Error ? error.message : String(error),
948
+ stack: error instanceof Error ? error.stack : undefined,
949
+ isDefect: !Cause.isFailure(cause),
950
+ });
951
+ return Deferred.fail(deferred, error instanceof Error ? error : new Error(String(error)));
952
+ }),
953
+ ),
954
+ );
955
+ yield* this._trackFiber(this._inFlightFibers, fiber);
956
+ yield* Effect.forkDaemon(
957
+ Fiber.await(fiber).pipe(Effect.andThen(() => this._untrackFiber(this._inFlightFibers, fiber))),
958
+ );
959
+
960
+ return deferred;
961
+ }).pipe(semaphore.withPermits(1));
962
+
963
+ // Wait for result outside the semaphore so multiple waiters can proceed concurrently.
964
+ return yield* Deferred.await(deferredToAwait);
965
+ });
966
+
967
+ private _contributeCapabilities(
968
+ module: Plugin.PluginModule,
969
+ capabilities: Capability.Any[],
970
+ ): Effect.Effect<void, Error> {
971
+ return Effect.gen(this, function* () {
972
+ capabilities.forEach((capability) => {
973
+ this.capabilities.contribute({ module: module.id, ...capability });
974
+ });
975
+ this._update(this._activeAtom, (active) => [...active, module.id]);
976
+ this._capabilities.set(module.id, capabilities);
977
+ });
978
+ }
979
+
980
+ private _deactivateModule(module: Plugin.PluginModule): Effect.Effect<boolean, Error> {
981
+ return Effect.gen(this, function* () {
982
+ const id = module.id;
983
+ log('deactivating', { id });
984
+ this._moduleMemoMap.delete(id);
985
+
986
+ const capabilities = this._capabilities.get(id);
987
+ if (capabilities) {
988
+ for (const capability of capabilities) {
989
+ this.capabilities.remove(capability.interface, capability.implementation);
990
+ const program = capability.deactivate?.() ?? Effect.succeed(undefined);
991
+ yield* program;
992
+ }
993
+ this._capabilities.delete(id);
994
+ }
995
+
996
+ const activeIndex = this._get(this._activeAtom).findIndex((event) => event === id);
997
+ if (activeIndex !== -1) {
998
+ this._update(this._activeAtom, (active) => active.filter((event) => event !== id));
999
+ }
1000
+
1001
+ log('deactivated', { id });
1002
+ return true;
1003
+ });
1004
+ }
1005
+ }
1006
+
1007
+ /**
1008
+ * Creates a new Plugin Manager instance.
1009
+ */
1010
+ export const make = (options: ManagerOptions): PluginManager => new ManagerImpl(options);
1011
+
1012
+ /**
1013
+ * Runs an effect concurrently with another effect.
1014
+ * If the first effect completes, the second effect is interrupted.
1015
+ */
1016
+ // TODO(dmaretskyi): Effect.race > Effect.asVoid
1017
+ const together =
1018
+ <R1>(togetherEffect: Effect.Effect<void, never, R1>) =>
1019
+ <A, E, R2>(effect: Effect.Effect<A, E, R2>): Effect.Effect<A, E, R1 | R2> =>
1020
+ Effect.gen(function* () {
1021
+ const togetherFiber = yield* Effect.fork(togetherEffect);
1022
+ const result = yield* effect;
1023
+ yield* Fiber.interrupt(togetherFiber);
1024
+ return result;
1025
+ });