@dxos/app-framework 0.8.4-main.c85a9c8dae → 0.8.4-main.d05539e30a

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 (387) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/{capability-7RLVE42K.mjs → capability-K5XIVCQU.mjs} +12 -11
  4. package/dist/lib/browser/capability-K5XIVCQU.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-5AHASNDW.mjs +95 -0
  6. package/dist/lib/browser/chunk-5AHASNDW.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-5GY3YOEL.mjs +28 -0
  8. package/dist/lib/browser/chunk-5GY3YOEL.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-PKQT6C53.mjs → chunk-66IXTIVK.mjs} +3 -2
  10. package/dist/lib/browser/chunk-66IXTIVK.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-ZRWBPIZG.mjs → chunk-BRK6GYNB.mjs} +14 -42
  12. package/dist/lib/browser/chunk-BRK6GYNB.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-FJ4765WW.mjs +8 -0
  14. package/dist/lib/browser/{chunk-FHQTHCX7.mjs.map → chunk-FJ4765WW.mjs.map} +3 -3
  15. package/dist/lib/browser/chunk-FO3IYSLV.mjs +68 -0
  16. package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-YNFPIQGB.mjs → chunk-IW44C7UL.mjs} +9 -2
  18. package/dist/lib/browser/chunk-IW44C7UL.mjs.map +7 -0
  19. package/dist/lib/browser/{chunk-5RJNZV7K.mjs → chunk-KFDF7KR3.mjs} +11 -13
  20. package/dist/lib/browser/{chunk-5RJNZV7K.mjs.map → chunk-KFDF7KR3.mjs.map} +3 -3
  21. package/dist/lib/browser/chunk-KLHQNYJ2.mjs +422 -0
  22. package/dist/lib/browser/chunk-KLHQNYJ2.mjs.map +7 -0
  23. package/dist/lib/browser/chunk-QLML5QFJ.mjs +581 -0
  24. package/dist/lib/browser/chunk-QLML5QFJ.mjs.map +7 -0
  25. package/dist/lib/browser/{chunk-FNKT2QQ2.mjs → chunk-SLX73WRZ.mjs} +90 -17
  26. package/dist/lib/browser/chunk-SLX73WRZ.mjs.map +7 -0
  27. package/dist/lib/browser/chunk-UVTGHZQF.mjs +513 -0
  28. package/dist/lib/browser/chunk-UVTGHZQF.mjs.map +7 -0
  29. package/dist/lib/browser/chunk-VJ5PFAWC.mjs +1446 -0
  30. package/dist/lib/browser/chunk-VJ5PFAWC.mjs.map +7 -0
  31. package/dist/lib/browser/cli/index.mjs +17 -32
  32. package/dist/lib/browser/cli/index.mjs.map +3 -3
  33. package/dist/lib/browser/common/activation-events.mjs +11 -14
  34. package/dist/lib/browser/common/capabilities.mjs +19 -8
  35. package/dist/lib/browser/core/activation-event.mjs +1 -1
  36. package/dist/lib/browser/core/capability.mjs +5 -1
  37. package/dist/lib/browser/core/plugin-manager.mjs +8 -4
  38. package/dist/lib/browser/core/plugin.mjs +16 -4
  39. package/dist/lib/browser/core/url-loader.mjs +24 -0
  40. package/dist/lib/browser/index.mjs +47 -49
  41. package/dist/lib/browser/index.mjs.map +4 -4
  42. package/dist/lib/browser/meta.json +1 -1
  43. package/dist/lib/browser/process-manager-capability-JIWLN7SU.mjs +89 -0
  44. package/dist/lib/browser/process-manager-capability-JIWLN7SU.mjs.map +7 -0
  45. package/dist/lib/browser/testing/index.mjs +199 -56
  46. package/dist/lib/browser/testing/index.mjs.map +4 -4
  47. package/dist/lib/browser/testing/react.mjs +78 -0
  48. package/dist/lib/browser/testing/react.mjs.map +7 -0
  49. package/dist/lib/browser/ui/index.mjs +24 -15
  50. package/dist/lib/node-esm/{capability-EVZK4REM.mjs → capability-RLKFFLTB.mjs} +12 -11
  51. package/dist/lib/node-esm/capability-RLKFFLTB.mjs.map +7 -0
  52. package/dist/lib/node-esm/{chunk-UEWJDI2L.mjs → chunk-37Z53PXZ.mjs} +2 -2
  53. package/dist/lib/node-esm/{chunk-UEWJDI2L.mjs.map → chunk-37Z53PXZ.mjs.map} +3 -3
  54. package/dist/lib/node-esm/chunk-42UNAKYO.mjs +423 -0
  55. package/dist/lib/node-esm/chunk-42UNAKYO.mjs.map +7 -0
  56. package/dist/lib/node-esm/{chunk-CJCQS2YL.mjs → chunk-6S45OMUP.mjs} +90 -17
  57. package/dist/lib/node-esm/chunk-6S45OMUP.mjs.map +7 -0
  58. package/dist/lib/node-esm/{chunk-2A4PRBIX.mjs → chunk-BYHYYJZH.mjs} +14 -42
  59. package/dist/lib/node-esm/chunk-BYHYYJZH.mjs.map +7 -0
  60. package/dist/lib/node-esm/{chunk-SB5ODNPX.mjs → chunk-CTKEZHKF.mjs} +9 -2
  61. package/dist/lib/node-esm/chunk-CTKEZHKF.mjs.map +7 -0
  62. package/dist/lib/node-esm/chunk-JNT72ZCN.mjs +514 -0
  63. package/dist/lib/node-esm/chunk-JNT72ZCN.mjs.map +7 -0
  64. package/dist/lib/node-esm/chunk-KFZEB6BV.mjs +29 -0
  65. package/dist/lib/node-esm/chunk-KFZEB6BV.mjs.map +7 -0
  66. package/dist/lib/node-esm/chunk-LJNUFNDO.mjs +582 -0
  67. package/dist/lib/node-esm/chunk-LJNUFNDO.mjs.map +7 -0
  68. package/dist/lib/node-esm/{chunk-VUIUFIGT.mjs → chunk-OUEMWPIW.mjs} +11 -13
  69. package/dist/lib/node-esm/{chunk-VUIUFIGT.mjs.map → chunk-OUEMWPIW.mjs.map} +3 -3
  70. package/dist/lib/node-esm/chunk-PW2VYGOS.mjs +96 -0
  71. package/dist/lib/node-esm/chunk-PW2VYGOS.mjs.map +7 -0
  72. package/dist/lib/node-esm/chunk-SFYCO3PT.mjs +1447 -0
  73. package/dist/lib/node-esm/chunk-SFYCO3PT.mjs.map +7 -0
  74. package/dist/lib/node-esm/chunk-WK7OIQKI.mjs +70 -0
  75. package/dist/lib/node-esm/chunk-WK7OIQKI.mjs.map +7 -0
  76. package/dist/lib/node-esm/{chunk-7OWSHPYK.mjs → chunk-XOCUANHO.mjs} +3 -2
  77. package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +7 -0
  78. package/dist/lib/node-esm/cli/index.mjs +17 -32
  79. package/dist/lib/node-esm/cli/index.mjs.map +3 -3
  80. package/dist/lib/node-esm/common/activation-events.mjs +11 -14
  81. package/dist/lib/node-esm/common/capabilities.mjs +19 -8
  82. package/dist/lib/node-esm/core/activation-event.mjs +1 -1
  83. package/dist/lib/node-esm/core/capability.mjs +5 -1
  84. package/dist/lib/node-esm/core/plugin-manager.mjs +8 -4
  85. package/dist/lib/node-esm/core/plugin.mjs +16 -4
  86. package/dist/lib/node-esm/core/url-loader.mjs +25 -0
  87. package/dist/lib/node-esm/index.mjs +47 -49
  88. package/dist/lib/node-esm/index.mjs.map +4 -4
  89. package/dist/lib/node-esm/meta.json +1 -1
  90. package/dist/lib/node-esm/process-manager-capability-PHKLO2BL.mjs +90 -0
  91. package/dist/lib/node-esm/process-manager-capability-PHKLO2BL.mjs.map +7 -0
  92. package/dist/lib/node-esm/testing/index.mjs +199 -56
  93. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  94. package/dist/lib/node-esm/testing/react.mjs +79 -0
  95. package/dist/lib/node-esm/testing/react.mjs.map +7 -0
  96. package/dist/lib/node-esm/ui/index.mjs +24 -15
  97. package/dist/plugin/node-esm/index.mjs +893 -0
  98. package/dist/plugin/node-esm/index.mjs.map +7 -0
  99. package/dist/plugin/node-esm/meta.json +1 -0
  100. package/dist/types/src/cli/cli.d.ts +1 -3
  101. package/dist/types/src/cli/cli.d.ts.map +1 -1
  102. package/dist/types/src/common/activation-events.d.ts +10 -13
  103. package/dist/types/src/common/activation-events.d.ts.map +1 -1
  104. package/dist/types/src/common/annotations.d.ts +1 -0
  105. package/dist/types/src/common/annotations.d.ts.map +1 -0
  106. package/dist/types/src/common/capabilities.d.ts +113 -12
  107. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  108. package/dist/types/src/common/operations.d.ts +8 -22
  109. package/dist/types/src/common/operations.d.ts.map +1 -1
  110. package/dist/types/src/core/activation-event.d.ts +5 -5
  111. package/dist/types/src/core/activation-event.d.ts.map +1 -1
  112. package/dist/types/src/core/capability-manager.d.ts +5 -0
  113. package/dist/types/src/core/capability-manager.d.ts.map +1 -1
  114. package/dist/types/src/core/capability.d.ts +13 -2
  115. package/dist/types/src/core/capability.d.ts.map +1 -1
  116. package/dist/types/src/core/edge-registry-plugin-provider.d.ts +30 -0
  117. package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -0
  118. package/dist/types/src/core/index.d.ts +6 -0
  119. package/dist/types/src/core/index.d.ts.map +1 -1
  120. package/dist/types/src/core/plugin-asset-cache.d.ts +71 -0
  121. package/dist/types/src/core/plugin-asset-cache.d.ts.map +1 -0
  122. package/dist/types/src/core/plugin-manager.d.ts +238 -7
  123. package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
  124. package/dist/types/src/core/plugin-manifest.d.ts +101 -0
  125. package/dist/types/src/core/plugin-manifest.d.ts.map +1 -0
  126. package/dist/types/src/core/plugin-manifest.test.d.ts +2 -0
  127. package/dist/types/src/core/plugin-manifest.test.d.ts.map +1 -0
  128. package/dist/types/src/core/plugin.d.ts +182 -7
  129. package/dist/types/src/core/plugin.d.ts.map +1 -1
  130. package/dist/types/src/core/registry.d.ts +107 -0
  131. package/dist/types/src/core/registry.d.ts.map +1 -0
  132. package/dist/types/src/core/url-loader.d.ts +127 -0
  133. package/dist/types/src/core/url-loader.d.ts.map +1 -0
  134. package/dist/types/src/core/url-loader.test.d.ts +2 -0
  135. package/dist/types/src/core/url-loader.test.d.ts.map +1 -0
  136. package/dist/types/src/helpers.d.ts.map +1 -1
  137. package/dist/types/src/index.d.ts +1 -2
  138. package/dist/types/src/index.d.ts.map +1 -1
  139. package/dist/types/src/plugin-process-manager/ProcessManagerPlugin.d.ts +3 -0
  140. package/dist/types/src/plugin-process-manager/ProcessManagerPlugin.d.ts.map +1 -0
  141. package/dist/types/src/plugin-process-manager/history/capability.d.ts.map +1 -0
  142. package/dist/types/src/plugin-process-manager/history/errors.d.ts +32 -0
  143. package/dist/types/src/plugin-process-manager/history/errors.d.ts.map +1 -0
  144. package/dist/types/src/{plugin-operation → plugin-process-manager}/history/history-tracker.d.ts +1 -1
  145. package/dist/types/src/plugin-process-manager/history/history-tracker.d.ts.map +1 -0
  146. package/dist/types/src/plugin-process-manager/history/history-tracker.test.d.ts.map +1 -0
  147. package/dist/types/src/plugin-process-manager/history/index.d.ts.map +1 -0
  148. package/dist/types/src/{plugin-operation → plugin-process-manager}/history/types.d.ts +1 -1
  149. package/dist/types/src/plugin-process-manager/history/types.d.ts.map +1 -0
  150. package/dist/types/src/{plugin-operation → plugin-process-manager}/history/undo-mapping.d.ts +1 -1
  151. package/dist/types/src/plugin-process-manager/history/undo-mapping.d.ts.map +1 -0
  152. package/dist/types/src/{plugin-operation → plugin-process-manager}/history/undo-registry.d.ts +1 -1
  153. package/dist/types/src/plugin-process-manager/history/undo-registry.d.ts.map +1 -0
  154. package/dist/types/src/plugin-process-manager/history/undo-registry.test.d.ts.map +1 -0
  155. package/dist/types/src/plugin-process-manager/index.d.ts +3 -0
  156. package/dist/types/src/plugin-process-manager/index.d.ts.map +1 -0
  157. package/dist/types/src/plugin-process-manager/meta.d.ts.map +1 -0
  158. package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts +8 -0
  159. package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts.map +1 -0
  160. package/dist/types/src/plugin-process-manager/testing.d.ts +59 -0
  161. package/dist/types/src/plugin-process-manager/testing.d.ts.map +1 -0
  162. package/dist/types/src/testing/harness.d.ts +79 -0
  163. package/dist/types/src/testing/harness.d.ts.map +1 -0
  164. package/dist/types/src/testing/index.d.ts +1 -0
  165. package/dist/types/src/testing/index.d.ts.map +1 -1
  166. package/dist/types/src/testing/react.d.ts +27 -0
  167. package/dist/types/src/testing/react.d.ts.map +1 -0
  168. package/dist/types/src/testing/react.test.d.ts +2 -0
  169. package/dist/types/src/testing/react.test.d.ts.map +1 -0
  170. package/dist/types/src/testing/service.d.ts.map +1 -1
  171. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  172. package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
  173. package/dist/types/src/ui/components/App/App.d.ts +3 -2
  174. package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
  175. package/dist/types/src/ui/components/App/App.stories.d.ts +2 -2
  176. package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -1
  177. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts +64 -0
  178. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts.map +1 -0
  179. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts +19 -0
  180. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts.map +1 -0
  181. package/dist/types/src/ui/components/Placeholder/index.d.ts +2 -0
  182. package/dist/types/src/ui/components/Placeholder/index.d.ts.map +1 -0
  183. package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -1
  184. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +16 -4
  185. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
  186. package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
  187. package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +1 -1
  188. package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts +48 -0
  189. package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -0
  190. package/dist/types/src/ui/components/Surface/index.d.ts +22 -6
  191. package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
  192. package/dist/types/src/ui/components/Surface/types.d.ts +110 -9
  193. package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
  194. package/dist/types/src/ui/components/Surface/types.test.d.ts +2 -0
  195. package/dist/types/src/ui/components/Surface/types.test.d.ts.map +1 -0
  196. package/dist/types/src/ui/components/index.d.ts +1 -0
  197. package/dist/types/src/ui/components/index.d.ts.map +1 -1
  198. package/dist/types/src/ui/hooks/index.d.ts +1 -1
  199. package/dist/types/src/ui/hooks/index.d.ts.map +1 -1
  200. package/dist/types/src/ui/hooks/useApp.d.ts +47 -11
  201. package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
  202. package/dist/types/src/ui/hooks/useApp.test.d.ts +2 -0
  203. package/dist/types/src/ui/hooks/useApp.test.d.ts.map +1 -0
  204. package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
  205. package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -1
  206. package/dist/types/src/ui/hooks/useProcessManagerRuntime.d.ts +24 -0
  207. package/dist/types/src/ui/hooks/useProcessManagerRuntime.d.ts.map +1 -0
  208. package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -1
  209. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +34 -0
  210. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +1 -0
  211. package/dist/types/src/vite-plugin/boot-loader/index.d.ts +2 -0
  212. package/dist/types/src/vite-plugin/boot-loader/index.d.ts.map +1 -0
  213. package/dist/types/src/vite-plugin/boot-loader/loader.d.ts +51 -0
  214. package/dist/types/src/vite-plugin/boot-loader/loader.d.ts.map +1 -0
  215. package/dist/types/src/vite-plugin/composer/index.d.ts +34 -0
  216. package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -0
  217. package/dist/types/src/vite-plugin/import-map/index.d.ts +28 -0
  218. package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -0
  219. package/dist/types/src/vite-plugin/index.d.ts +5 -0
  220. package/dist/types/src/vite-plugin/index.d.ts.map +1 -0
  221. package/dist/types/src/vite-plugin/manifest.d.ts +41 -0
  222. package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -0
  223. package/dist/types/src/vite-plugin/manifest.test.d.ts +2 -0
  224. package/dist/types/src/vite-plugin/manifest.test.d.ts.map +1 -0
  225. package/dist/types/src/vite-plugin/packages.d.ts +13 -0
  226. package/dist/types/src/vite-plugin/packages.d.ts.map +1 -0
  227. package/dist/types/tsconfig.tsbuildinfo +1 -1
  228. package/moon.yml +15 -0
  229. package/package.json +53 -54
  230. package/src/cli/cli.ts +4 -9
  231. package/src/common/activation-events.ts +12 -17
  232. package/src/common/annotations.ts +3 -0
  233. package/src/common/capabilities.ts +160 -29
  234. package/src/common/operations.ts +7 -10
  235. package/src/context.ts +1 -1
  236. package/src/core/activation-event.ts +5 -2
  237. package/src/core/capability-manager.test.ts +1 -1
  238. package/src/core/capability-manager.ts +22 -1
  239. package/src/core/capability.ts +20 -2
  240. package/src/core/edge-registry-plugin-provider.ts +92 -0
  241. package/src/core/index.ts +6 -0
  242. package/src/core/plugin-asset-cache.ts +60 -0
  243. package/src/core/plugin-manager.test.ts +1085 -31
  244. package/src/core/plugin-manager.ts +1170 -198
  245. package/src/core/plugin-manifest.test.ts +75 -0
  246. package/src/core/plugin-manifest.ts +134 -0
  247. package/src/core/plugin.ts +194 -12
  248. package/src/core/registry.ts +163 -0
  249. package/src/core/url-loader.test.ts +221 -0
  250. package/src/core/url-loader.ts +388 -0
  251. package/src/index.ts +1 -2
  252. package/src/plugin-process-manager/ProcessManagerPlugin.ts +24 -0
  253. package/src/{plugin-operation → plugin-process-manager}/history/capability.ts +1 -2
  254. package/src/plugin-process-manager/history/errors.ts +7 -0
  255. package/src/{plugin-operation → plugin-process-manager}/history/history-tracker.test.ts +37 -43
  256. package/src/{plugin-operation → plugin-process-manager}/history/history-tracker.ts +1 -2
  257. package/src/{plugin-operation → plugin-process-manager}/history/types.ts +1 -1
  258. package/src/{plugin-operation → plugin-process-manager}/history/undo-mapping.ts +1 -1
  259. package/src/{plugin-operation → plugin-process-manager}/history/undo-registry.test.ts +3 -4
  260. package/src/{plugin-operation → plugin-process-manager}/history/undo-registry.ts +1 -1
  261. package/src/{plugin-operation → plugin-process-manager}/index.ts +1 -1
  262. package/src/plugin-process-manager/meta.ts +14 -0
  263. package/src/plugin-process-manager/process-manager-capability.ts +178 -0
  264. package/src/{plugin-operation → plugin-process-manager}/testing.ts +26 -45
  265. package/src/testing/harness.ts +247 -0
  266. package/src/testing/index.ts +1 -0
  267. package/src/testing/react.test.tsx +48 -0
  268. package/src/testing/react.tsx +113 -0
  269. package/src/testing/service.ts +4 -4
  270. package/src/testing/withPluginManager.stories.tsx +1 -2
  271. package/src/testing/withPluginManager.tsx +45 -20
  272. package/src/ui/components/App/App.stories.tsx +7 -13
  273. package/src/ui/components/App/App.tsx +29 -5
  274. package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
  275. package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
  276. package/src/{plugin-runtime → ui/components/Placeholder}/index.ts +1 -1
  277. package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +8 -7
  278. package/src/ui/components/Surface/SurfaceComponent.stories.tsx +16 -15
  279. package/src/ui/components/Surface/SurfaceComponent.tsx +111 -55
  280. package/src/ui/components/Surface/SurfaceInfo.tsx +0 -1
  281. package/src/ui/components/Surface/SurfaceProfilerContext.tsx +207 -0
  282. package/src/ui/components/Surface/index.ts +35 -1
  283. package/src/ui/components/Surface/types.test.ts +126 -0
  284. package/src/ui/components/Surface/types.ts +164 -12
  285. package/src/ui/components/index.ts +1 -0
  286. package/src/ui/hooks/index.ts +1 -1
  287. package/src/ui/hooks/useApp.test.tsx +159 -0
  288. package/src/ui/hooks/useApp.tsx +229 -24
  289. package/src/ui/hooks/useLoading.tsx +14 -6
  290. package/src/ui/hooks/useProcessManagerRuntime.ts +68 -0
  291. package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +270 -0
  292. package/src/vite-plugin/boot-loader/boot-loader.css +320 -0
  293. package/src/vite-plugin/boot-loader/boot-loader.js +325 -0
  294. package/src/vite-plugin/boot-loader/index.ts +5 -0
  295. package/src/vite-plugin/boot-loader/loader.ts +123 -0
  296. package/src/vite-plugin/composer/index.ts +306 -0
  297. package/src/vite-plugin/import-map/index.ts +527 -0
  298. package/src/vite-plugin/index.ts +10 -0
  299. package/src/vite-plugin/manifest.test.ts +46 -0
  300. package/src/vite-plugin/manifest.ts +57 -0
  301. package/src/vite-plugin/packages.ts +187 -0
  302. package/tsconfig.json +25 -1
  303. package/tsconfig.node.json +1 -1
  304. package/vitest.config.ts +1 -1
  305. package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
  306. package/dist/lib/browser/capability-2GL5JAGJ.mjs +0 -37
  307. package/dist/lib/browser/capability-2GL5JAGJ.mjs.map +0 -7
  308. package/dist/lib/browser/capability-7RLVE42K.mjs.map +0 -7
  309. package/dist/lib/browser/chunk-4CTRO67U.mjs +0 -703
  310. package/dist/lib/browser/chunk-4CTRO67U.mjs.map +0 -7
  311. package/dist/lib/browser/chunk-FHQTHCX7.mjs +0 -8
  312. package/dist/lib/browser/chunk-FNKT2QQ2.mjs.map +0 -7
  313. package/dist/lib/browser/chunk-HE27PNNQ.mjs +0 -824
  314. package/dist/lib/browser/chunk-HE27PNNQ.mjs.map +0 -7
  315. package/dist/lib/browser/chunk-NPUEVX42.mjs +0 -34
  316. package/dist/lib/browser/chunk-NPUEVX42.mjs.map +0 -7
  317. package/dist/lib/browser/chunk-PKQT6C53.mjs.map +0 -7
  318. package/dist/lib/browser/chunk-REORGDJT.mjs +0 -80
  319. package/dist/lib/browser/chunk-REORGDJT.mjs.map +0 -7
  320. package/dist/lib/browser/chunk-YAFEA4GV.mjs +0 -1
  321. package/dist/lib/browser/chunk-YNFPIQGB.mjs.map +0 -7
  322. package/dist/lib/browser/chunk-ZRWBPIZG.mjs.map +0 -7
  323. package/dist/lib/browser/invoker-capability-BNLVNYHU.mjs +0 -36
  324. package/dist/lib/browser/invoker-capability-BNLVNYHU.mjs.map +0 -7
  325. package/dist/lib/node-esm/capability-CHIMU6LX.mjs +0 -38
  326. package/dist/lib/node-esm/capability-CHIMU6LX.mjs.map +0 -7
  327. package/dist/lib/node-esm/capability-EVZK4REM.mjs.map +0 -7
  328. package/dist/lib/node-esm/chunk-2A4PRBIX.mjs.map +0 -7
  329. package/dist/lib/node-esm/chunk-7CPNAEGV.mjs +0 -704
  330. package/dist/lib/node-esm/chunk-7CPNAEGV.mjs.map +0 -7
  331. package/dist/lib/node-esm/chunk-7OWSHPYK.mjs.map +0 -7
  332. package/dist/lib/node-esm/chunk-CJCQS2YL.mjs.map +0 -7
  333. package/dist/lib/node-esm/chunk-DTCHT2X2.mjs +0 -825
  334. package/dist/lib/node-esm/chunk-DTCHT2X2.mjs.map +0 -7
  335. package/dist/lib/node-esm/chunk-JAZVHID3.mjs +0 -35
  336. package/dist/lib/node-esm/chunk-JAZVHID3.mjs.map +0 -7
  337. package/dist/lib/node-esm/chunk-SB5ODNPX.mjs.map +0 -7
  338. package/dist/lib/node-esm/chunk-UFW652GS.mjs +0 -81
  339. package/dist/lib/node-esm/chunk-UFW652GS.mjs.map +0 -7
  340. package/dist/lib/node-esm/chunk-Z4TJPSMP.mjs +0 -2
  341. package/dist/lib/node-esm/invoker-capability-VF6SP44V.mjs +0 -37
  342. package/dist/lib/node-esm/invoker-capability-VF6SP44V.mjs.map +0 -7
  343. package/dist/types/src/plugin-operation/OperationPlugin.d.ts +0 -3
  344. package/dist/types/src/plugin-operation/OperationPlugin.d.ts.map +0 -1
  345. package/dist/types/src/plugin-operation/history/capability.d.ts.map +0 -1
  346. package/dist/types/src/plugin-operation/history/errors.d.ts +0 -5
  347. package/dist/types/src/plugin-operation/history/errors.d.ts.map +0 -1
  348. package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +0 -1
  349. package/dist/types/src/plugin-operation/history/history-tracker.test.d.ts.map +0 -1
  350. package/dist/types/src/plugin-operation/history/index.d.ts.map +0 -1
  351. package/dist/types/src/plugin-operation/history/types.d.ts.map +0 -1
  352. package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +0 -1
  353. package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +0 -1
  354. package/dist/types/src/plugin-operation/history/undo-registry.test.d.ts.map +0 -1
  355. package/dist/types/src/plugin-operation/index.d.ts +0 -3
  356. package/dist/types/src/plugin-operation/index.d.ts.map +0 -1
  357. package/dist/types/src/plugin-operation/invoker-capability.d.ts +0 -6
  358. package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +0 -1
  359. package/dist/types/src/plugin-operation/meta.d.ts.map +0 -1
  360. package/dist/types/src/plugin-operation/testing.d.ts +0 -109
  361. package/dist/types/src/plugin-operation/testing.d.ts.map +0 -1
  362. package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts +0 -3
  363. package/dist/types/src/plugin-runtime/RuntimePlugin.d.ts.map +0 -1
  364. package/dist/types/src/plugin-runtime/capability.d.ts +0 -6
  365. package/dist/types/src/plugin-runtime/capability.d.ts.map +0 -1
  366. package/dist/types/src/plugin-runtime/index.d.ts +0 -2
  367. package/dist/types/src/plugin-runtime/index.d.ts.map +0 -1
  368. package/dist/types/src/plugin-runtime/meta.d.ts +0 -3
  369. package/dist/types/src/plugin-runtime/meta.d.ts.map +0 -1
  370. package/dist/types/src/ui/hooks/useOperationResolver.d.ts +0 -19
  371. package/dist/types/src/ui/hooks/useOperationResolver.d.ts.map +0 -1
  372. package/src/plugin-operation/OperationPlugin.ts +0 -25
  373. package/src/plugin-operation/history/errors.ts +0 -11
  374. package/src/plugin-operation/invoker-capability.ts +0 -40
  375. package/src/plugin-operation/meta.ts +0 -11
  376. package/src/plugin-runtime/RuntimePlugin.ts +0 -20
  377. package/src/plugin-runtime/capability.ts +0 -53
  378. package/src/plugin-runtime/meta.ts +0 -11
  379. package/src/ui/hooks/useOperationResolver.ts +0 -40
  380. /package/dist/lib/browser/{chunk-YAFEA4GV.mjs.map → core/url-loader.mjs.map} +0 -0
  381. /package/dist/lib/node-esm/{chunk-Z4TJPSMP.mjs.map → core/url-loader.mjs.map} +0 -0
  382. /package/dist/types/src/{plugin-operation → plugin-process-manager}/history/capability.d.ts +0 -0
  383. /package/dist/types/src/{plugin-operation → plugin-process-manager}/history/history-tracker.test.d.ts +0 -0
  384. /package/dist/types/src/{plugin-operation → plugin-process-manager}/history/index.d.ts +0 -0
  385. /package/dist/types/src/{plugin-operation → plugin-process-manager}/history/undo-registry.test.d.ts +0 -0
  386. /package/dist/types/src/{plugin-operation → plugin-process-manager}/meta.d.ts +0 -0
  387. /package/src/{plugin-operation → plugin-process-manager}/history/index.ts +0 -0
@@ -2,10 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { afterEach, assert, describe, it } from '@effect/vitest';
6
5
  import { type Atom, Registry } from '@effect-atom/atom-react';
6
+ import { afterEach, assert, describe, it } from '@effect/vitest';
7
+ import * as Cause from 'effect/Cause';
7
8
  import * as Duration from 'effect/Duration';
8
9
  import * as Effect from 'effect/Effect';
10
+ import * as Exit from 'effect/Exit';
9
11
  import * as Fiber from 'effect/Fiber';
10
12
  import * as Match from 'effect/Match';
11
13
  import * as PubSub from 'effect/PubSub';
@@ -16,21 +18,20 @@ import { invariant } from '@dxos/invariant';
16
18
  import { type LogConfig, type LogEntry, LogLevel, log } from '@dxos/log';
17
19
 
18
20
  import { ActivationEvents } from '../common';
19
-
20
21
  import * as ActivationEvent from './activation-event';
21
22
  import * as Capability from './capability';
22
23
  import type * as CapabilityManager from './capability-manager';
23
24
  import * as Plugin from './plugin';
24
25
  import * as PluginManager from './plugin-manager';
25
26
 
26
- const String = Capability.make<{ string: string }>('dxos.org/test/string');
27
- const Number = Capability.make<{ number: number }>('dxos.org/test/number');
28
- const Total = Capability.make<{ total: number }>('dxos.org/test/total');
27
+ const String = Capability.make<{ string: string }>('org.dxos.test.string');
28
+ const Number = Capability.make<{ number: number }>('org.dxos.test.number');
29
+ const Total = Capability.make<{ total: number }>('org.dxos.test.total');
29
30
 
30
- const CountEvent = ActivationEvent.make('dxos.org/test/count');
31
- const FailEvent = ActivationEvent.make('dxos.org/test/fail');
31
+ const CountEvent = ActivationEvent.make('org.dxos.test.count');
32
+ const FailEvent = ActivationEvent.make('org.dxos.test.fail');
32
33
 
33
- const testMeta = { id: 'dxos.org/plugin/test', name: 'Test' };
34
+ const testMeta = { id: 'org.dxos.plugin.test', name: 'Test' };
34
35
 
35
36
  // TODO(wittjosiah): Factor out?
36
37
  const atomCounter = (registry: Registry.Registry, atom: Atom.Atom<any>) => {
@@ -60,7 +61,7 @@ describe('PluginManager', () => {
60
61
  const pluginLoader = Effect.fn(function* (id: string) {
61
62
  const plugin = plugins.find((plugin) => plugin.meta.id === id);
62
63
  invariant(plugin, `Plugin not found: ${id}`);
63
- return plugin;
64
+ return { plugin };
64
65
  });
65
66
 
66
67
  afterEach(() => {
@@ -75,14 +76,138 @@ describe('PluginManager', () => {
75
76
 
76
77
  const manager = PluginManager.make({ pluginLoader });
77
78
  const added = yield* manager.add(testMeta.id);
78
- assert.isTrue(added);
79
+ assert.strictEqual(added, testPlugin);
79
80
  assert.deepStrictEqual(manager.getPlugins(), [testPlugin]);
80
- const removed = manager.remove(testMeta.id);
81
+ assert.deepStrictEqual(manager.getEnabled(), []);
82
+ const removed = yield* manager.remove(testMeta.id);
81
83
  assert.isTrue(removed);
82
84
  assert.deepStrictEqual(manager.getPlugins(), []);
83
85
  }),
84
86
  );
85
87
 
88
+ it.effect('should add plugin when locator differs from meta.id', () =>
89
+ Effect.gen(function* () {
90
+ const Test = Plugin.make(Plugin.define(testMeta));
91
+ const testPlugin = Test();
92
+
93
+ const urlLocator = 'https://example.com/plugin.mjs';
94
+ const urlLoader = Effect.fn(function* (locator: string) {
95
+ if (locator === urlLocator) {
96
+ return { plugin: testPlugin };
97
+ }
98
+ return yield* Effect.fail(new Error(`Unknown locator: ${locator}`));
99
+ });
100
+
101
+ const manager = PluginManager.make({ pluginLoader: urlLoader });
102
+ const added = yield* manager.add(urlLocator);
103
+ assert.strictEqual(added, testPlugin);
104
+ assert.deepStrictEqual(manager.getPlugins(), [testPlugin]);
105
+ assert.deepStrictEqual(manager.getEnabled(), []);
106
+ yield* manager.enable(added.meta.id);
107
+ assert.deepStrictEqual(manager.getEnabled(), [testMeta.id]);
108
+ }),
109
+ );
110
+
111
+ it.effect('dev plugin shadows an existing plugin with the same id', () =>
112
+ Effect.gen(function* () {
113
+ const productionPlugin = Plugin.make(
114
+ Plugin.define(testMeta).pipe(
115
+ Plugin.addModule({
116
+ id: 'Prod',
117
+ activatesOn: ActivationEvents.Startup,
118
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'prod' })),
119
+ }),
120
+ ),
121
+ )();
122
+ const devPlugin = Plugin.make(
123
+ Plugin.define(testMeta).pipe(
124
+ Plugin.addModule({
125
+ id: 'Dev',
126
+ activatesOn: ActivationEvents.Startup,
127
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'dev' })),
128
+ }),
129
+ ),
130
+ )();
131
+
132
+ const loader = Effect.fn(function* (locator: string) {
133
+ if (locator === 'prod') {
134
+ return { plugin: productionPlugin };
135
+ }
136
+ if (locator === 'dev') {
137
+ return { plugin: devPlugin, dev: true };
138
+ }
139
+ return yield* Effect.fail(new Error(`Unknown locator: ${locator}`));
140
+ });
141
+
142
+ const manager = PluginManager.make({ pluginLoader: loader });
143
+ yield* manager.add('prod');
144
+ yield* manager.enable(testMeta.id);
145
+ yield* manager.activate(ActivationEvents.Startup);
146
+ assert.deepStrictEqual(
147
+ manager.capabilities.getAll(String).map((value) => value.string),
148
+ ['prod'],
149
+ );
150
+
151
+ // Loading the dev plugin with the same id swaps it into the id slot.
152
+ yield* manager.add('dev');
153
+ yield* manager.enable(testMeta.id);
154
+ assert.strictEqual(
155
+ manager.getPlugins().find((plugin) => plugin.meta.id === testMeta.id),
156
+ devPlugin,
157
+ );
158
+ yield* manager.reset(ActivationEvents.Startup);
159
+ assert.deepStrictEqual(
160
+ manager.capabilities.getAll(String).map((value) => value.string),
161
+ ['dev'],
162
+ );
163
+
164
+ // Removing the dev plugin restores the original and re-enables it
165
+ // because it was enabled at shadow time.
166
+ yield* manager.remove(testMeta.id);
167
+ assert.strictEqual(
168
+ manager.getPlugins().find((plugin) => plugin.meta.id === testMeta.id),
169
+ productionPlugin,
170
+ );
171
+ assert.isTrue(manager.getEnabled().includes(testMeta.id));
172
+ yield* manager.reset(ActivationEvents.Startup);
173
+ assert.deepStrictEqual(
174
+ manager.capabilities.getAll(String).map((value) => value.string),
175
+ ['prod'],
176
+ );
177
+ }),
178
+ );
179
+
180
+ it.effect('dev plugin add does not auto-enable a previously-disabled shadow target', () =>
181
+ Effect.gen(function* () {
182
+ const productionPlugin = Plugin.make(Plugin.define(testMeta))();
183
+ const devPlugin = Plugin.make(Plugin.define(testMeta))();
184
+ const loader = Effect.fn(function* (locator: string) {
185
+ if (locator === 'prod') {
186
+ return { plugin: productionPlugin };
187
+ }
188
+ if (locator === 'dev') {
189
+ return { plugin: devPlugin, dev: true };
190
+ }
191
+ return yield* Effect.fail(new Error(`Unknown locator: ${locator}`));
192
+ });
193
+
194
+ const manager = PluginManager.make({ pluginLoader: loader });
195
+ yield* manager.add('prod');
196
+ // Production plugin is registered but explicitly NOT enabled.
197
+ assert.deepStrictEqual(manager.getEnabled(), []);
198
+
199
+ yield* manager.add('dev');
200
+ yield* manager.remove(testMeta.id);
201
+
202
+ // Original is restored but stays disabled, matching its pre-shadow state.
203
+ assert.strictEqual(
204
+ manager.getPlugins().find((plugin) => plugin.meta.id === testMeta.id),
205
+ productionPlugin,
206
+ );
207
+ assert.deepStrictEqual(manager.getEnabled(), []);
208
+ }),
209
+ );
210
+
86
211
  it.effect('should support factory pattern with options', () =>
87
212
  Effect.gen(function* () {
88
213
  type TestPluginOptions = { count: number };
@@ -103,7 +228,7 @@ describe('PluginManager', () => {
103
228
  const plugin = TestPluginFactory({ count: 5 });
104
229
  plugins = [plugin];
105
230
 
106
- const manager = PluginManager.make({ plugins: [plugin], core: [], pluginLoader });
231
+ const manager = PluginManager.make({ plugins: [plugin], pluginLoader });
107
232
  yield* manager.enable(testMeta.id);
108
233
  yield* manager.activate(ActivationEvents.Startup);
109
234
  const strings = manager.capabilities.getAll(String);
@@ -125,7 +250,7 @@ describe('PluginManager', () => {
125
250
  );
126
251
 
127
252
  const testPlugin = Test();
128
- const manager = PluginManager.make({ plugins: [testPlugin], core: [], pluginLoader });
253
+ const manager = PluginManager.make({ plugins: [testPlugin], pluginLoader });
129
254
  yield* manager.enable(testMeta.id);
130
255
  assert.deepStrictEqual(manager.getEnabled(), [Test.meta.id]);
131
256
  assert.deepStrictEqual(manager.getModules(), [testPlugin.modules[0]]);
@@ -197,6 +322,7 @@ describe('PluginManager', () => {
197
322
 
198
323
  const manager = PluginManager.make({ pluginLoader });
199
324
  yield* manager.add(testMeta.id);
325
+ yield* manager.enable(testMeta.id);
200
326
  const error = yield* Effect.flip(manager.activate(FailEvent));
201
327
  assert.strictEqual(error.message, 'test');
202
328
  }),
@@ -204,7 +330,7 @@ describe('PluginManager', () => {
204
330
 
205
331
  it.effect('should catch and log defects (synchronous throws) in module activation', () =>
206
332
  Effect.gen(function* () {
207
- const DefectEvent = ActivationEvent.make('dxos.org/test/defect');
333
+ const DefectEvent = ActivationEvent.make('org.dxos.test.defect');
208
334
  const capturedErrors: LogEntry[] = [];
209
335
  const removeProcessor = log.addProcessor((_config: LogConfig, entry: LogEntry) => {
210
336
  if (entry.level === LogLevel.ERROR) {
@@ -229,6 +355,7 @@ describe('PluginManager', () => {
229
355
 
230
356
  const manager = PluginManager.make({ pluginLoader });
231
357
  yield* manager.add(testMeta.id);
358
+ yield* manager.enable(testMeta.id);
232
359
  const error = yield* Effect.flip(manager.activate(DefectEvent));
233
360
 
234
361
  // Verify the error was caught and propagated.
@@ -238,7 +365,7 @@ describe('PluginManager', () => {
238
365
  const defectLog = capturedErrors.find(
239
366
  (entry) =>
240
367
  entry.message?.includes('module failed to activate') &&
241
- entry.context?.module === 'dxos.org/plugin/test/module/DefectInEffectSync',
368
+ entry.context?.module === 'org.dxos.plugin.test.module.DefectInEffectSync',
242
369
  );
243
370
  assert.isNotNull(defectLog, 'Expected error log for defect');
244
371
  assert.strictEqual(defectLog?.context?.isDefect, true, 'Expected isDefect to be true for synchronous throw');
@@ -249,7 +376,7 @@ describe('PluginManager', () => {
249
376
 
250
377
  it.effect('should catch and log defects when activate throws before returning Effect', () =>
251
378
  Effect.gen(function* () {
252
- const DefectEvent = ActivationEvent.make('dxos.org/test/defect-immediate');
379
+ const DefectEvent = ActivationEvent.make('org.dxos.test.defect-immediate');
253
380
  const capturedErrors: LogEntry[] = [];
254
381
  const removeProcessor = log.addProcessor((_config: LogConfig, entry: LogEntry) => {
255
382
  if (entry.level === LogLevel.ERROR) {
@@ -275,6 +402,7 @@ describe('PluginManager', () => {
275
402
 
276
403
  const manager = PluginManager.make({ pluginLoader });
277
404
  yield* manager.add(testMeta.id);
405
+ yield* manager.enable(testMeta.id);
278
406
  const error = yield* Effect.flip(manager.activate(DefectEvent));
279
407
 
280
408
  // Verify the error was caught and propagated.
@@ -284,7 +412,7 @@ describe('PluginManager', () => {
284
412
  const defectLog = capturedErrors.find(
285
413
  (entry) =>
286
414
  entry.message?.includes('module failed to activate') &&
287
- entry.context?.module === 'dxos.org/plugin/test/module/DefectImmediate',
415
+ entry.context?.module === 'org.dxos.plugin.test.module.DefectImmediate',
288
416
  );
289
417
  assert.isNotNull(defectLog, 'Expected error log for immediate defect');
290
418
  assert.strictEqual(
@@ -339,6 +467,7 @@ describe('PluginManager', () => {
339
467
  );
340
468
 
341
469
  yield* manager.add(testMeta.id);
470
+ yield* manager.enable(testMeta.id);
342
471
  yield* manager.activate(ActivationEvents.Startup);
343
472
  yield* activating.await;
344
473
  yield* activated.await;
@@ -389,6 +518,7 @@ describe('PluginManager', () => {
389
518
 
390
519
  {
391
520
  yield* manager.add(testMeta.id);
521
+ yield* manager.enable(testMeta.id);
392
522
  const result = yield* manager.activate(ActivationEvents.Startup);
393
523
  assert.isTrue(result);
394
524
  assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
@@ -419,7 +549,7 @@ describe('PluginManager', () => {
419
549
 
420
550
  it.effect('should be able to fire custom activation events', () =>
421
551
  Effect.gen(function* () {
422
- const Plugin1 = Plugin.define({ id: 'dxos.org/test/plugin-1', name: 'Plugin 1' }).pipe(
552
+ const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
423
553
  Plugin.addModule({
424
554
  activatesOn: CountEvent,
425
555
  id: 'Plugin1',
@@ -427,7 +557,7 @@ describe('PluginManager', () => {
427
557
  }),
428
558
  Plugin.make,
429
559
  );
430
- const Plugin2 = Plugin.define({ id: 'dxos.org/test/plugin-2', name: 'Plugin 2' }).pipe(
560
+ const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
431
561
  Plugin.addModule({
432
562
  activatesOn: CountEvent,
433
563
  id: 'Plugin2',
@@ -435,7 +565,7 @@ describe('PluginManager', () => {
435
565
  }),
436
566
  Plugin.make,
437
567
  );
438
- const Plugin3 = Plugin.define({ id: 'dxos.org/test/plugin-3', name: 'Plugin 3' }).pipe(
568
+ const Plugin3 = Plugin.define({ id: 'org.dxos.test.plugin-3', name: 'Plugin 3' }).pipe(
439
569
  Plugin.addModule({
440
570
  activatesOn: CountEvent,
441
571
  id: 'Plugin3',
@@ -453,16 +583,19 @@ describe('PluginManager', () => {
453
583
  assert.strictEqual(manager.capabilities.getAll(Number).length, 0);
454
584
 
455
585
  yield* manager.add(Plugin1.meta.id);
586
+ yield* manager.enable(Plugin1.meta.id);
456
587
  yield* manager.activate(CountEvent);
457
588
  assert.deepStrictEqual(manager.getActive(), [plugin1.modules[0].id]);
458
589
  assert.strictEqual(manager.capabilities.getAll(Number).length, 1);
459
590
 
460
591
  yield* manager.add(Plugin2.meta.id);
592
+ yield* manager.enable(Plugin2.meta.id);
461
593
  yield* manager.activate(CountEvent);
462
594
  assert.deepStrictEqual(manager.getActive(), [plugin1.modules[0].id, plugin2.modules[0].id]);
463
595
  assert.strictEqual(manager.capabilities.getAll(Number).length, 2);
464
596
 
465
597
  yield* manager.add(Plugin3.meta.id);
598
+ yield* manager.enable(Plugin3.meta.id);
466
599
  yield* manager.activate(CountEvent);
467
600
  assert.deepStrictEqual(manager.getActive(), [
468
601
  plugin1.modules[0].id,
@@ -493,6 +626,7 @@ describe('PluginManager', () => {
493
626
  assert.strictEqual(manager.capabilities.getAll(String).length, 0);
494
627
 
495
628
  yield* manager.add(testMeta.id);
629
+ yield* manager.enable(testMeta.id);
496
630
  yield* manager.activate(ActivationEvents.Startup);
497
631
  assert.deepStrictEqual(manager.getActive(), []);
498
632
  assert.strictEqual(manager.capabilities.getAll(String).length, 0);
@@ -526,6 +660,7 @@ describe('PluginManager', () => {
526
660
  assert.strictEqual(count, 0);
527
661
 
528
662
  yield* manager.add(testMeta.id);
663
+ yield* manager.enable(testMeta.id);
529
664
  yield* manager.activate(CountEvent);
530
665
  assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
531
666
  assert.strictEqual(manager.capabilities.getAll(String).length, 1);
@@ -546,11 +681,11 @@ describe('PluginManager', () => {
546
681
  state.total = numbers.reduce((acc: number, n: { number: number }) => acc + n.number, 0);
547
682
  };
548
683
 
549
- const Count = Plugin.define({ id: 'dxos.org/test/count', name: 'Count' }).pipe(
684
+ const Count = Plugin.define({ id: 'org.dxos.test.count', name: 'Count' }).pipe(
550
685
  Plugin.addModule({
551
686
  id: 'Count',
552
687
  activatesOn: ActivationEvents.Startup,
553
- activatesBefore: [CountEvent],
688
+ firesBeforeActivation: [CountEvent],
554
689
  activate: Effect.fnUntraced(function* () {
555
690
  const capabilityManager = yield* Capability.Service;
556
691
  computeTotal(capabilityManager);
@@ -585,7 +720,9 @@ describe('PluginManager', () => {
585
720
  const manager = PluginManager.make({ pluginLoader });
586
721
  {
587
722
  yield* manager.add(Test.meta.id);
723
+ yield* manager.enable(Test.meta.id);
588
724
  yield* manager.add(Count.meta.id);
725
+ yield* manager.enable(Count.meta.id);
589
726
  yield* manager.activate(ActivationEvents.Startup);
590
727
  assert.deepStrictEqual(manager.getActive(), [
591
728
  ...testPlugin.modules.map((m) => m.id),
@@ -626,7 +763,7 @@ describe('PluginManager', () => {
626
763
 
627
764
  it.effect('should be reactive', () =>
628
765
  Effect.gen(function* () {
629
- const Plugin1 = Plugin.define({ id: 'dxos.org/test/plugin-1', name: 'Plugin 1' }).pipe(
766
+ const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
630
767
  Plugin.addModule({
631
768
  activatesOn: CountEvent,
632
769
  id: 'Plugin1',
@@ -634,7 +771,7 @@ describe('PluginManager', () => {
634
771
  }),
635
772
  Plugin.make,
636
773
  );
637
- const Plugin2 = Plugin.define({ id: 'dxos.org/test/plugin-2', name: 'Plugin 2' }).pipe(
774
+ const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
638
775
  Plugin.addModule({
639
776
  activatesOn: CountEvent,
640
777
  id: 'Plugin2',
@@ -642,7 +779,7 @@ describe('PluginManager', () => {
642
779
  }),
643
780
  Plugin.make,
644
781
  );
645
- const Plugin3 = Plugin.define({ id: 'dxos.org/test/plugin-3', name: 'Plugin 3' }).pipe(
782
+ const Plugin3 = Plugin.define({ id: 'org.dxos.test.plugin-3', name: 'Plugin 3' }).pipe(
646
783
  Plugin.addModule({
647
784
  activatesOn: CountEvent,
648
785
  id: 'Plugin3',
@@ -668,6 +805,7 @@ describe('PluginManager', () => {
668
805
  assert.strictEqual(pendingResetUpdates.count, 0);
669
806
 
670
807
  yield* manager.add(Plugin1.meta.id);
808
+ yield* manager.enable(Plugin1.meta.id);
671
809
  assert.strictEqual(pluginUpdates.count, 1);
672
810
  assert.strictEqual(enabledUpdates.count, 1);
673
811
  assert.strictEqual(modulesUpdates.count, 1);
@@ -684,6 +822,7 @@ describe('PluginManager', () => {
684
822
  assert.strictEqual(pendingResetUpdates.count, 0);
685
823
 
686
824
  yield* manager.add(Plugin2.meta.id);
825
+ yield* manager.enable(Plugin2.meta.id);
687
826
  assert.strictEqual(pluginUpdates.count, 2);
688
827
  assert.strictEqual(enabledUpdates.count, 2);
689
828
  assert.strictEqual(modulesUpdates.count, 2);
@@ -700,6 +839,7 @@ describe('PluginManager', () => {
700
839
  assert.strictEqual(pendingResetUpdates.count, 2);
701
840
 
702
841
  yield* manager.add(Plugin3.meta.id);
842
+ yield* manager.enable(Plugin3.meta.id);
703
843
  assert.strictEqual(pluginUpdates.count, 3);
704
844
  assert.strictEqual(enabledUpdates.count, 3);
705
845
  assert.strictEqual(modulesUpdates.count, 3);
@@ -724,7 +864,7 @@ describe('PluginManager', () => {
724
864
  assert.strictEqual(eventsFiredUpdates.count, 1);
725
865
  assert.strictEqual(pendingResetUpdates.count, 4);
726
866
 
727
- manager.remove(Plugin1.meta.id);
867
+ yield* manager.remove(Plugin1.meta.id);
728
868
  assert.strictEqual(pluginUpdates.count, 4);
729
869
  assert.strictEqual(enabledUpdates.count, 4);
730
870
  assert.strictEqual(modulesUpdates.count, 4);
@@ -752,8 +892,8 @@ describe('PluginManager', () => {
752
892
  }
753
893
  });
754
894
 
755
- const SlowEvent = ActivationEvent.make('dxos.org/test/slow');
756
- const SlowPlugin = Plugin.define({ id: 'dxos.org/test/slow-plugin', name: 'Slow Plugin' }).pipe(
895
+ const SlowEvent = ActivationEvent.make('org.dxos.test.slow');
896
+ const SlowPlugin = Plugin.define({ id: 'org.dxos.test.slow-plugin', name: 'Slow Plugin' }).pipe(
757
897
  Plugin.addModule({
758
898
  id: 'SlowModule',
759
899
  activatesOn: SlowEvent,
@@ -771,6 +911,7 @@ describe('PluginManager', () => {
771
911
 
772
912
  const manager = PluginManager.make({ pluginLoader });
773
913
  yield* manager.add(SlowPlugin.meta.id);
914
+ yield* manager.enable(SlowPlugin.meta.id);
774
915
 
775
916
  // Fork the activation so we can control time with TestClock.
776
917
  const activationFiber = yield* Effect.fork(manager.activate(SlowEvent));
@@ -795,11 +936,11 @@ describe('PluginManager', () => {
795
936
  it.effect('should prevent concurrent loads of the same module via semaphore', () =>
796
937
  Effect.gen(function* () {
797
938
  // Two different events that both can trigger the same module.
798
- const EventA = ActivationEvent.make('dxos.org/test/event-a');
799
- const EventB = ActivationEvent.make('dxos.org/test/event-b');
939
+ const EventA = ActivationEvent.make('org.dxos.test.event-a');
940
+ const EventB = ActivationEvent.make('org.dxos.test.event-b');
800
941
 
801
942
  let activateCallCount = 0;
802
- const ConcurrentPlugin = Plugin.define({ id: 'dxos.org/test/concurrent-plugin', name: 'Concurrent Plugin' }).pipe(
943
+ const ConcurrentPlugin = Plugin.define({ id: 'org.dxos.test.concurrent-plugin', name: 'Concurrent Plugin' }).pipe(
803
944
  Plugin.addModule({
804
945
  id: 'ConcurrentModule',
805
946
  // Module activates on either event - this allows two different events to race.
@@ -819,6 +960,7 @@ describe('PluginManager', () => {
819
960
 
820
961
  const manager = PluginManager.make({ pluginLoader });
821
962
  yield* manager.add(ConcurrentPlugin.meta.id);
963
+ yield* manager.enable(ConcurrentPlugin.meta.id);
822
964
 
823
965
  // Fork two concurrent activations with DIFFERENT events.
824
966
  // Both events trigger the same module, so both will try to call _loadModule.
@@ -842,4 +984,916 @@ describe('PluginManager', () => {
842
984
  assert.strictEqual(strings[0].string, 'concurrent');
843
985
  }),
844
986
  );
987
+
988
+ it.effect('should deactivate all active modules on shutdown', () =>
989
+ Effect.gen(function* () {
990
+ const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
991
+ Plugin.addModule({
992
+ activatesOn: ActivationEvents.Startup,
993
+ id: 'Plugin1',
994
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
995
+ }),
996
+ Plugin.make,
997
+ );
998
+ const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
999
+ Plugin.addModule({
1000
+ activatesOn: ActivationEvents.Startup,
1001
+ id: 'Plugin2',
1002
+ activate: () => Effect.succeed(Capability.contributes(Number, { number: 42 })),
1003
+ }),
1004
+ Plugin.make,
1005
+ );
1006
+ const plugin1 = Plugin1();
1007
+ const plugin2 = Plugin2();
1008
+ plugins = [plugin1, plugin2];
1009
+
1010
+ const manager = PluginManager.make({ pluginLoader });
1011
+ yield* manager.add(Plugin1.meta.id);
1012
+ yield* manager.enable(Plugin1.meta.id);
1013
+ yield* manager.add(Plugin2.meta.id);
1014
+ yield* manager.enable(Plugin2.meta.id);
1015
+ yield* manager.activate(ActivationEvents.Startup);
1016
+ assert.strictEqual(manager.getActive().length, 2);
1017
+ assert.strictEqual(manager.capabilities.getAll(String).length, 1);
1018
+ assert.strictEqual(manager.capabilities.getAll(Number).length, 1);
1019
+
1020
+ const result = yield* manager.shutdown();
1021
+ assert.isTrue(result);
1022
+ assert.deepStrictEqual(manager.getActive(), []);
1023
+ assert.strictEqual(manager.capabilities.getAll(String).length, 0);
1024
+ assert.strictEqual(manager.capabilities.getAll(Number).length, 0);
1025
+ }),
1026
+ );
1027
+
1028
+ it.effect('should run capability deactivate hooks during shutdown', () =>
1029
+ Effect.gen(function* () {
1030
+ let deactivated = false;
1031
+ const Test = Plugin.define(testMeta).pipe(
1032
+ Plugin.addModule({
1033
+ id: 'WithDeactivate',
1034
+ activatesOn: ActivationEvents.Startup,
1035
+ activate: () =>
1036
+ Effect.succeed(
1037
+ Capability.contributes(String, { string: 'hello' }, () =>
1038
+ Effect.sync(() => {
1039
+ deactivated = true;
1040
+ }),
1041
+ ),
1042
+ ),
1043
+ }),
1044
+ Plugin.make,
1045
+ );
1046
+ const testPlugin = Test();
1047
+ plugins = [testPlugin];
1048
+
1049
+ const manager = PluginManager.make({ pluginLoader });
1050
+ yield* manager.add(testMeta.id);
1051
+ yield* manager.enable(testMeta.id);
1052
+ yield* manager.activate(ActivationEvents.Startup);
1053
+ assert.isFalse(deactivated);
1054
+
1055
+ yield* manager.shutdown();
1056
+ assert.isTrue(deactivated);
1057
+ }),
1058
+ );
1059
+
1060
+ it.effect('should deactivate modules in reverse activation order during shutdown', () =>
1061
+ Effect.gen(function* () {
1062
+ const deactivationOrder: string[] = [];
1063
+ const Plugin1 = Plugin.define({ id: 'org.dxos.test.plugin-1', name: 'Plugin 1' }).pipe(
1064
+ Plugin.addModule({
1065
+ activatesOn: ActivationEvents.Startup,
1066
+ id: 'First',
1067
+ activate: () =>
1068
+ Effect.succeed(
1069
+ Capability.contributes(String, { string: 'first' }, () =>
1070
+ Effect.sync(() => {
1071
+ deactivationOrder.push('First');
1072
+ }),
1073
+ ),
1074
+ ),
1075
+ }),
1076
+ Plugin.make,
1077
+ );
1078
+ const Plugin2 = Plugin.define({ id: 'org.dxos.test.plugin-2', name: 'Plugin 2' }).pipe(
1079
+ Plugin.addModule({
1080
+ activatesOn: ActivationEvents.Startup,
1081
+ id: 'Second',
1082
+ activate: () =>
1083
+ Effect.succeed(
1084
+ Capability.contributes(Number, { number: 2 }, () =>
1085
+ Effect.sync(() => {
1086
+ deactivationOrder.push('Second');
1087
+ }),
1088
+ ),
1089
+ ),
1090
+ }),
1091
+ Plugin.make,
1092
+ );
1093
+ plugins = [Plugin1(), Plugin2()];
1094
+
1095
+ const manager = PluginManager.make({ pluginLoader });
1096
+ yield* manager.add(Plugin1.meta.id);
1097
+ yield* manager.enable(Plugin1.meta.id);
1098
+ yield* manager.add(Plugin2.meta.id);
1099
+ yield* manager.enable(Plugin2.meta.id);
1100
+ yield* manager.activate(ActivationEvents.Startup);
1101
+
1102
+ yield* manager.shutdown();
1103
+ assert.deepStrictEqual(deactivationOrder, ['Second', 'First']);
1104
+ }),
1105
+ );
1106
+
1107
+ it.effect('should clear lifecycle bookkeeping during shutdown', () =>
1108
+ Effect.gen(function* () {
1109
+ const Test = Plugin.define(testMeta).pipe(
1110
+ Plugin.addModule({
1111
+ id: 'Hello',
1112
+ activatesOn: ActivationEvents.Startup,
1113
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
1114
+ }),
1115
+ Plugin.make,
1116
+ );
1117
+ const testPlugin = Test();
1118
+ plugins = [testPlugin];
1119
+
1120
+ const manager = PluginManager.make({ pluginLoader });
1121
+ yield* manager.add(testMeta.id);
1122
+ yield* manager.enable(testMeta.id);
1123
+ yield* manager.activate(ActivationEvents.Startup);
1124
+ assert.isTrue(manager.getEventsFired().length > 0);
1125
+
1126
+ yield* manager.shutdown();
1127
+ assert.deepStrictEqual(manager.getEventsFired(), []);
1128
+ assert.deepStrictEqual(manager.getPendingReset(), []);
1129
+ assert.deepStrictEqual(manager.getActive(), []);
1130
+ }),
1131
+ );
1132
+
1133
+ it.effect('should interrupt in-flight activation during shutdown', () =>
1134
+ Effect.gen(function* () {
1135
+ const activationStarted = yield* Effect.makeLatch(false);
1136
+ const allowActivationToComplete = yield* Effect.makeLatch(false);
1137
+ const Test = Plugin.define(testMeta).pipe(
1138
+ Plugin.addModule({
1139
+ id: 'Hello',
1140
+ activatesOn: ActivationEvents.Startup,
1141
+ activate: () =>
1142
+ Effect.gen(function* () {
1143
+ yield* activationStarted.open;
1144
+ yield* allowActivationToComplete.await;
1145
+ return Capability.contributes(String, { string: 'hello' });
1146
+ }),
1147
+ }),
1148
+ Plugin.make,
1149
+ );
1150
+ const testPlugin = Test();
1151
+ plugins = [testPlugin];
1152
+
1153
+ const manager = PluginManager.make({ pluginLoader });
1154
+ yield* manager.add(testMeta.id);
1155
+ yield* manager.enable(testMeta.id);
1156
+
1157
+ const activationFiber = yield* Effect.fork(manager.activate(ActivationEvents.Startup));
1158
+ yield* activationStarted.await;
1159
+
1160
+ const shutdownFiber = yield* Effect.fork(manager.shutdown());
1161
+ yield* allowActivationToComplete.open;
1162
+
1163
+ const shutdownResult = yield* Fiber.join(shutdownFiber);
1164
+ const activationExit = yield* Fiber.await(activationFiber);
1165
+
1166
+ assert.isTrue(shutdownResult);
1167
+ assert.isTrue(Exit.isFailure(activationExit));
1168
+ if (Exit.isFailure(activationExit)) {
1169
+ assert.isTrue(Cause.isInterruptedOnly(activationExit.cause));
1170
+ }
1171
+ assert.strictEqual(manager.capabilities.getAll(String).length, 0);
1172
+ assert.deepStrictEqual(manager.getActive(), []);
1173
+ assert.deepStrictEqual(manager.getEventsFired(), []);
1174
+ }),
1175
+ );
1176
+
1177
+ it.effect('should preserve plugins, core, enabled, and modules after shutdown', () =>
1178
+ Effect.gen(function* () {
1179
+ const Test = Plugin.define(testMeta).pipe(
1180
+ Plugin.addModule({
1181
+ id: 'Hello',
1182
+ activatesOn: ActivationEvents.Startup,
1183
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
1184
+ }),
1185
+ Plugin.make,
1186
+ );
1187
+ const testPlugin = Test();
1188
+ plugins = [testPlugin];
1189
+
1190
+ const manager = PluginManager.make({ pluginLoader });
1191
+ yield* manager.add(testMeta.id);
1192
+ yield* manager.enable(testMeta.id);
1193
+ yield* manager.activate(ActivationEvents.Startup);
1194
+
1195
+ const pluginsBefore = manager.getPlugins();
1196
+ const coreBefore = manager.getCore();
1197
+ const enabledBefore = manager.getEnabled();
1198
+ const modulesBefore = manager.getModules();
1199
+
1200
+ yield* manager.shutdown();
1201
+
1202
+ assert.deepStrictEqual(manager.getPlugins(), pluginsBefore);
1203
+ assert.deepStrictEqual(manager.getCore(), coreBefore);
1204
+ assert.deepStrictEqual(manager.getEnabled(), enabledBefore);
1205
+ assert.deepStrictEqual(manager.getModules(), modulesBefore);
1206
+ }),
1207
+ );
1208
+
1209
+ it.effect('should allow re-activation after shutdown', () =>
1210
+ Effect.gen(function* () {
1211
+ let activateCount = 0;
1212
+ const Test = Plugin.define(testMeta).pipe(
1213
+ Plugin.addModule({
1214
+ id: 'Hello',
1215
+ activatesOn: ActivationEvents.Startup,
1216
+ activate: () => {
1217
+ activateCount++;
1218
+ return Effect.succeed(Capability.contributes(String, { string: 'hello' }));
1219
+ },
1220
+ }),
1221
+ Plugin.make,
1222
+ );
1223
+ const testPlugin = Test();
1224
+ plugins = [testPlugin];
1225
+
1226
+ const manager = PluginManager.make({ pluginLoader });
1227
+ yield* manager.add(testMeta.id);
1228
+ yield* manager.enable(testMeta.id);
1229
+ yield* manager.activate(ActivationEvents.Startup);
1230
+ assert.strictEqual(activateCount, 1);
1231
+ assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
1232
+
1233
+ yield* manager.shutdown();
1234
+ assert.deepStrictEqual(manager.getActive(), []);
1235
+
1236
+ yield* manager.activate(ActivationEvents.Startup);
1237
+ assert.strictEqual(activateCount, 2);
1238
+ assert.deepStrictEqual(manager.getActive(), [testPlugin.modules[0].id]);
1239
+ assert.strictEqual(manager.capabilities.getAll(String).length, 1);
1240
+ }),
1241
+ );
1242
+
1243
+ describe('Plugin.lazy', () => {
1244
+ const lazyMeta = { id: 'org.dxos.plugin.lazy', name: 'Lazy' };
1245
+
1246
+ it('exposes meta synchronously without invoking the loader', () => {
1247
+ let loaderCalls = 0;
1248
+ const Real = Plugin.make(Plugin.define<void>(lazyMeta));
1249
+ const LazyTest = Plugin.lazy(lazyMeta, () => {
1250
+ loaderCalls++;
1251
+ return Promise.resolve({ default: Real });
1252
+ });
1253
+
1254
+ assert.strictEqual(LazyTest.meta.id, lazyMeta.id);
1255
+ assert.strictEqual(LazyTest.meta.name, 'Lazy');
1256
+ assert.strictEqual(loaderCalls, 0);
1257
+
1258
+ const stub = LazyTest();
1259
+ assert.strictEqual(stub.meta.id, lazyMeta.id);
1260
+ assert.deepStrictEqual([...stub.modules], []);
1261
+ assert.isTrue(Plugin.isLazy(stub));
1262
+ assert.strictEqual(loaderCalls, 0);
1263
+ });
1264
+
1265
+ it.effect('resolves the loader on enable and registers the real plugin modules', () =>
1266
+ Effect.gen(function* () {
1267
+ let loaderCalls = 0;
1268
+ const Real = Plugin.define(lazyMeta).pipe(
1269
+ Plugin.addModule({
1270
+ id: 'Hello',
1271
+ activatesOn: ActivationEvents.Startup,
1272
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
1273
+ }),
1274
+ Plugin.make,
1275
+ );
1276
+ const LazyTest = Plugin.lazy(lazyMeta, () => {
1277
+ loaderCalls++;
1278
+ return Promise.resolve({ default: Real });
1279
+ });
1280
+
1281
+ const lazyStub = LazyTest();
1282
+ plugins = [lazyStub];
1283
+
1284
+ const manager = PluginManager.make({ pluginLoader });
1285
+ yield* manager.add(lazyMeta.id);
1286
+ // Loader has not been invoked yet — only meta is exposed.
1287
+ assert.strictEqual(loaderCalls, 0);
1288
+ assert.deepStrictEqual(manager.getModules(), []);
1289
+
1290
+ yield* manager.enable(lazyMeta.id);
1291
+ assert.strictEqual(loaderCalls, 1);
1292
+ // After enable the registered plugin should be the real one (not the stub),
1293
+ // and its modules should be registered with the manager.
1294
+ const registered = manager.getPlugins().find((p) => p.meta.id === lazyMeta.id);
1295
+ assert.isDefined(registered);
1296
+ assert.isFalse(Plugin.isLazy(registered!));
1297
+ assert.strictEqual(registered!.modules.length, 1);
1298
+
1299
+ yield* manager.activate(ActivationEvents.Startup);
1300
+ assert.strictEqual(manager.capabilities.getAll(String).length, 1);
1301
+ }),
1302
+ );
1303
+
1304
+ it.effect('does not invoke the loader if the plugin is never enabled', () =>
1305
+ Effect.gen(function* () {
1306
+ let loaderCalls = 0;
1307
+ const Real = Plugin.make(Plugin.define<void>(lazyMeta));
1308
+ const LazyTest = Plugin.lazy(lazyMeta, () => {
1309
+ loaderCalls++;
1310
+ return Promise.resolve({ default: Real });
1311
+ });
1312
+ const lazyStub = LazyTest();
1313
+ plugins = [lazyStub];
1314
+
1315
+ const manager = PluginManager.make({ pluginLoader });
1316
+ yield* manager.add(lazyMeta.id);
1317
+
1318
+ // Activate an event that has no listeners — the lazy plugin must not load.
1319
+ yield* manager.activate(ActivationEvents.Startup);
1320
+ assert.strictEqual(loaderCalls, 0);
1321
+ }),
1322
+ );
1323
+
1324
+ it.effect('forwards factory options to the real plugin factory', () =>
1325
+ Effect.gen(function* () {
1326
+ type Opts = { greeting: string };
1327
+ const RealFactory = (opts: Opts) =>
1328
+ Plugin.define(lazyMeta).pipe(
1329
+ Plugin.addModule({
1330
+ id: 'Hello',
1331
+ activatesOn: ActivationEvents.Startup,
1332
+ activate: () => Effect.succeed(Capability.contributes(String, { string: opts.greeting })),
1333
+ }),
1334
+ Plugin.make,
1335
+ )(undefined as void);
1336
+
1337
+ const RealFactoryWithMeta = Object.assign(RealFactory, { meta: lazyMeta });
1338
+
1339
+ const LazyTest = Plugin.lazy<Opts>(lazyMeta, () => Promise.resolve({ default: RealFactoryWithMeta }));
1340
+ const lazyStub = LazyTest({ greeting: 'hola' });
1341
+ plugins = [lazyStub];
1342
+
1343
+ const manager = PluginManager.make({ pluginLoader });
1344
+ yield* manager.add(lazyMeta.id);
1345
+ yield* manager.enable(lazyMeta.id);
1346
+ yield* manager.activate(ActivationEvents.Startup);
1347
+
1348
+ const all = manager.capabilities.getAll(String);
1349
+ assert.strictEqual(all.length, 1);
1350
+ assert.strictEqual(all[0].string, 'hola');
1351
+ }),
1352
+ );
1353
+
1354
+ it.effect('wraps loader rejections in a descriptive error', () =>
1355
+ Effect.gen(function* () {
1356
+ const LazyTest = Plugin.lazy(lazyMeta, () =>
1357
+ Promise.reject<{ default: Plugin.PluginFactory }>(new Error('boom')),
1358
+ );
1359
+ const lazyStub = LazyTest();
1360
+ plugins = [lazyStub];
1361
+
1362
+ const manager = PluginManager.make({ pluginLoader });
1363
+ yield* manager.add(lazyMeta.id);
1364
+
1365
+ const exit = yield* Effect.exit(manager.enable(lazyMeta.id));
1366
+ assert.isTrue(Exit.isFailure(exit));
1367
+ if (Exit.isFailure(exit)) {
1368
+ const failure = Cause.failureOption(exit.cause);
1369
+ assert.isTrue(failure._tag === 'Some');
1370
+ if (failure._tag === 'Some') {
1371
+ assert.isTrue(Plugin.LazyPluginError.is(failure.value));
1372
+ assert.strictEqual((failure.value as Plugin.LazyPluginError).context.id, lazyMeta.id);
1373
+ assert.strictEqual((failure.value as Plugin.LazyPluginError).context.reason, 'load-failed');
1374
+ }
1375
+ }
1376
+ }),
1377
+ );
1378
+
1379
+ it.effect('publishes a lazy:<id> error message when resolution fails', () =>
1380
+ Effect.gen(function* () {
1381
+ const LazyTest = Plugin.lazy(lazyMeta, () =>
1382
+ Promise.reject<{ default: Plugin.PluginFactory }>(new Error('boom')),
1383
+ );
1384
+ const lazyStub = LazyTest();
1385
+ plugins = [lazyStub];
1386
+
1387
+ const manager = PluginManager.make({ pluginLoader });
1388
+ // Subscribe first so we don't miss the activating/error pair.
1389
+ const queue = yield* PubSub.subscribe(manager.activation);
1390
+ yield* manager.add(lazyMeta.id);
1391
+ yield* Effect.exit(manager.enable(lazyMeta.id));
1392
+ const messages = yield* Queue.takeAll(queue);
1393
+
1394
+ const errorMessage = [...messages].find((m) => m.module === `lazy:${lazyMeta.id}` && m.state === 'error');
1395
+ assert.isDefined(errorMessage);
1396
+ assert.isDefined(errorMessage!.error);
1397
+ }).pipe(Effect.scoped),
1398
+ );
1399
+
1400
+ it.effect('coalesces concurrent lazy resolutions of the same plugin id', () =>
1401
+ Effect.gen(function* () {
1402
+ let factoryCalls = 0;
1403
+ // System-tagged so the constructor's core derivation picks it up,
1404
+ // forcing an implicit enable that races the explicit one below.
1405
+ const coreLazyMeta = { ...lazyMeta, tags: ['system'] };
1406
+ const Real = (() => {
1407
+ const inner = Plugin.make(
1408
+ Plugin.define<void>(coreLazyMeta).pipe(
1409
+ Plugin.addModule({
1410
+ id: 'Hello',
1411
+ activatesOn: ActivationEvents.Startup,
1412
+ activate: () => Effect.succeed(Capability.contributes(String, { string: 'hello' })),
1413
+ }),
1414
+ ),
1415
+ );
1416
+ const factory = (() => {
1417
+ factoryCalls++;
1418
+ return inner();
1419
+ }) as Plugin.PluginFactory;
1420
+ return Object.assign(factory, { meta: coreLazyMeta });
1421
+ })();
1422
+ const LazyTest = Plugin.lazy(coreLazyMeta, () => Promise.resolve({ default: Real }));
1423
+ const lazyStub = LazyTest();
1424
+ // `manager.enable(id)` is implicitly called twice — once from the
1425
+ // constructor's core/enabled chain, once from our explicit call. With
1426
+ // coalescing, the underlying factory should still run exactly once.
1427
+ plugins = [lazyStub];
1428
+ const manager = PluginManager.make({ pluginLoader, plugins });
1429
+ yield* manager.enable(coreLazyMeta.id);
1430
+ assert.strictEqual(factoryCalls, 1);
1431
+ }),
1432
+ );
1433
+
1434
+ it.effect('fails with a tagged error when the factory output is not a Plugin', () =>
1435
+ Effect.gen(function* () {
1436
+ const BadFactory = Object.assign(() => ({ not: 'a plugin' }) as any, { meta: lazyMeta });
1437
+ const LazyTest = Plugin.lazy(lazyMeta, () => Promise.resolve({ default: BadFactory }));
1438
+ const lazyStub = LazyTest();
1439
+ plugins = [lazyStub];
1440
+
1441
+ const manager = PluginManager.make({ pluginLoader });
1442
+ yield* manager.add(lazyMeta.id);
1443
+
1444
+ const exit = yield* Effect.exit(manager.enable(lazyMeta.id));
1445
+ assert.isTrue(Exit.isFailure(exit));
1446
+ if (Exit.isFailure(exit)) {
1447
+ const failure = Cause.failureOption(exit.cause);
1448
+ assert.isTrue(failure._tag === 'Some');
1449
+ if (failure._tag === 'Some') {
1450
+ assert.isTrue(Plugin.LazyPluginError.is(failure.value));
1451
+ assert.strictEqual((failure.value as Plugin.LazyPluginError).context.reason, 'invalid-plugin');
1452
+ }
1453
+ }
1454
+ }),
1455
+ );
1456
+ });
1457
+
1458
+ describe('timeouts and failure tracking', () => {
1459
+ // Atom subscriptions fire synchronously when the registry's `_set` runs,
1460
+ // even from a forked fiber on the default runtime. Wrapping in
1461
+ // `Effect.async` lets a TestClock-driven test wait for state produced by
1462
+ // a background `_runForkedFiber` (e.g. the auto-disable triggered when a
1463
+ // module activation times out) without relying on real-time `sleep`.
1464
+ const waitFor = <T>(registry: Registry.Registry, atom: Atom.Atom<T>, predicate: (value: T) => boolean) =>
1465
+ Effect.async<void>((resume) => {
1466
+ if (predicate(registry.get(atom))) {
1467
+ resume(Effect.void);
1468
+ return;
1469
+ }
1470
+ let resolved = false;
1471
+ const dispose = registry.subscribe(atom, () => {
1472
+ if (!resolved && predicate(registry.get(atom))) {
1473
+ resolved = true;
1474
+ dispose();
1475
+ resume(Effect.void);
1476
+ }
1477
+ });
1478
+ return Effect.sync(() => {
1479
+ if (!resolved) {
1480
+ dispose();
1481
+ }
1482
+ });
1483
+ });
1484
+
1485
+ it.effect('records and auto-disables a plugin whose module exceeds the activation timeout', () =>
1486
+ Effect.gen(function* () {
1487
+ const SlowEvent = ActivationEvent.make('org.dxos.test.activation-timeout');
1488
+ const SlowPlugin = Plugin.define({ id: 'org.dxos.test.slow-activation', name: 'Slow Activation' }).pipe(
1489
+ Plugin.addModule({
1490
+ id: 'Slow',
1491
+ activatesOn: SlowEvent,
1492
+ activate: Effect.fnUntraced(function* () {
1493
+ yield* Effect.sleep(Duration.seconds(60));
1494
+ return Capability.contributes(String, { string: 'never' });
1495
+ }),
1496
+ }),
1497
+ Plugin.make,
1498
+ );
1499
+ plugins = [SlowPlugin()];
1500
+
1501
+ const registry = Registry.make();
1502
+ const manager = PluginManager.make({
1503
+ pluginLoader,
1504
+ registry,
1505
+ activationTimeout: Duration.seconds(2),
1506
+ });
1507
+ yield* manager.add(SlowPlugin.meta.id);
1508
+ yield* manager.enable(SlowPlugin.meta.id);
1509
+
1510
+ const fiber = yield* Effect.fork(manager.activate(SlowEvent));
1511
+ // Push past the 2s activation timeout. The forked module fiber is on
1512
+ // TestClock too, so the timeout fires deterministically.
1513
+ yield* TestClock.adjust(Duration.seconds(3));
1514
+ const exit = yield* Fiber.await(fiber);
1515
+ assert.isTrue(Exit.isFailure(exit));
1516
+
1517
+ const failed = manager.getFailed();
1518
+ assert.strictEqual(failed.length, 1);
1519
+ assert.strictEqual(failed[0].id, SlowPlugin.meta.id);
1520
+ assert.strictEqual(failed[0].phase, 'activation');
1521
+ assert.strictEqual(failed[0].reason, 'timeout');
1522
+
1523
+ // Auto-disable runs in a forked fiber on the default runtime; wait for
1524
+ // the `enabled` atom to settle to the disabled state.
1525
+ yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(SlowPlugin.meta.id));
1526
+ assert.deepStrictEqual(manager.getEnabled(), []);
1527
+ }),
1528
+ );
1529
+
1530
+ it.effect('records and auto-disables a lazy plugin whose loader exceeds the load timeout', () =>
1531
+ Effect.gen(function* () {
1532
+ const lazyMeta = { id: 'org.dxos.test.slow-load', name: 'Slow Load' };
1533
+ // The dynamic import never resolves; the manager's load timeout should
1534
+ // surface this as a `LazyPluginError` whose `cause` is `PluginTimeoutError`.
1535
+ const LazyTest = Plugin.lazy(lazyMeta, () => new Promise<{ default: Plugin.PluginFactory }>(() => {}));
1536
+ plugins = [LazyTest()];
1537
+
1538
+ const registry = Registry.make();
1539
+ const manager = PluginManager.make({
1540
+ pluginLoader,
1541
+ registry,
1542
+ loadTimeout: Duration.seconds(1),
1543
+ });
1544
+ yield* manager.add(lazyMeta.id);
1545
+
1546
+ const enableFiber = yield* Effect.fork(manager.enable(lazyMeta.id));
1547
+ yield* TestClock.adjust(Duration.seconds(2));
1548
+ const exit = yield* Fiber.await(enableFiber);
1549
+ assert.isTrue(Exit.isFailure(exit));
1550
+
1551
+ // The wrapped `LazyPluginError` carries the timeout error as its cause.
1552
+ if (Exit.isFailure(exit)) {
1553
+ const failure = Cause.failureOption(exit.cause);
1554
+ if (failure._tag === 'Some') {
1555
+ assert.isTrue(Plugin.LazyPluginError.is(failure.value));
1556
+ const lazyError = failure.value as Plugin.LazyPluginError;
1557
+ assert.isTrue(PluginManager.PluginTimeoutError.is(lazyError.cause as Error));
1558
+ }
1559
+ }
1560
+
1561
+ const failed = manager.getFailed();
1562
+ assert.strictEqual(failed.length, 1);
1563
+ assert.strictEqual(failed[0].id, lazyMeta.id);
1564
+ assert.strictEqual(failed[0].phase, 'load');
1565
+ assert.strictEqual(failed[0].reason, 'timeout');
1566
+
1567
+ // The plugin was added to `enabled` before the lazy resolution failed,
1568
+ // so the auto-disable fork should clear it.
1569
+ yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(lazyMeta.id));
1570
+ }),
1571
+ );
1572
+
1573
+ it.effect('records non-timeout activation errors as reason: error', () =>
1574
+ Effect.gen(function* () {
1575
+ const FailingEvent = ActivationEvent.make('org.dxos.test.activation-error');
1576
+ const FailingPlugin = Plugin.define({ id: 'org.dxos.test.failing', name: 'Failing' }).pipe(
1577
+ Plugin.addModule({
1578
+ id: 'Boom',
1579
+ activatesOn: FailingEvent,
1580
+ activate: () => Effect.fail(new Error('boom')),
1581
+ }),
1582
+ Plugin.make,
1583
+ );
1584
+ plugins = [FailingPlugin()];
1585
+
1586
+ const registry = Registry.make();
1587
+ const manager = PluginManager.make({ pluginLoader, registry });
1588
+ yield* manager.add(FailingPlugin.meta.id);
1589
+ yield* manager.enable(FailingPlugin.meta.id);
1590
+
1591
+ const exit = yield* Effect.exit(manager.activate(FailingEvent));
1592
+ assert.isTrue(Exit.isFailure(exit));
1593
+
1594
+ const failed = manager.getFailed();
1595
+ assert.strictEqual(failed.length, 1);
1596
+ assert.strictEqual(failed[0].reason, 'error');
1597
+ assert.strictEqual(failed[0].error.message, 'boom');
1598
+
1599
+ yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(FailingPlugin.meta.id));
1600
+ }),
1601
+ );
1602
+
1603
+ it.effect('does not auto-disable a core plugin even though the failure is recorded', () =>
1604
+ Effect.gen(function* () {
1605
+ const FailingEvent = ActivationEvent.make('org.dxos.test.core-fail');
1606
+ const CorePlugin = Plugin.define({ id: 'org.dxos.test.core', name: 'Core', tags: ['system'] }).pipe(
1607
+ Plugin.addModule({
1608
+ id: 'Boom',
1609
+ activatesOn: FailingEvent,
1610
+ activate: () => Effect.fail(new Error('boom')),
1611
+ }),
1612
+ Plugin.make,
1613
+ );
1614
+ const corePlugin = CorePlugin();
1615
+ plugins = [corePlugin];
1616
+
1617
+ const manager = PluginManager.make({
1618
+ pluginLoader,
1619
+ plugins: [corePlugin],
1620
+ });
1621
+ // Core is auto-enabled via the constructor's enable chain.
1622
+ const exit = yield* Effect.exit(manager.activate(FailingEvent));
1623
+ assert.isTrue(Exit.isFailure(exit));
1624
+
1625
+ assert.strictEqual(manager.getFailed().length, 1);
1626
+ // Core stays enabled; host opted into it being non-removable.
1627
+ assert.deepStrictEqual(manager.getEnabled(), [corePlugin.meta.id]);
1628
+ }),
1629
+ );
1630
+
1631
+ it.effect('clearFailure removes the failure record and re-enable starts fresh', () =>
1632
+ Effect.gen(function* () {
1633
+ let shouldFail = true;
1634
+ const Event = ActivationEvent.make('org.dxos.test.flaky');
1635
+ const FlakyPlugin = Plugin.define({ id: 'org.dxos.test.flaky', name: 'Flaky' }).pipe(
1636
+ Plugin.addModule({
1637
+ id: 'Maybe',
1638
+ activatesOn: Event,
1639
+ activate: () =>
1640
+ shouldFail
1641
+ ? Effect.fail(new Error('first try'))
1642
+ : Effect.succeed(Capability.contributes(String, { string: 'ok' })),
1643
+ }),
1644
+ Plugin.make,
1645
+ );
1646
+ const flakyPlugin = FlakyPlugin();
1647
+ plugins = [flakyPlugin];
1648
+
1649
+ const registry = Registry.make();
1650
+ const manager = PluginManager.make({ pluginLoader, registry });
1651
+ yield* manager.add(flakyPlugin.meta.id);
1652
+ yield* manager.enable(flakyPlugin.meta.id);
1653
+
1654
+ yield* Effect.exit(manager.activate(Event));
1655
+ assert.strictEqual(manager.getFailed().length, 1);
1656
+ yield* waitFor(registry, manager.enabled, (ids) => !ids.includes(flakyPlugin.meta.id));
1657
+
1658
+ // Calling `enable` again clears the prior failure record before
1659
+ // attempting resolution; verify the explicit API does too.
1660
+ assert.isTrue(manager.clearFailure(flakyPlugin.meta.id));
1661
+ assert.strictEqual(manager.getFailed().length, 0);
1662
+ assert.isFalse(manager.clearFailure(flakyPlugin.meta.id));
1663
+
1664
+ // Retry: enable + reset the activation event so the module re-runs.
1665
+ shouldFail = false;
1666
+ yield* manager.enable(flakyPlugin.meta.id);
1667
+ yield* manager.reset(Event);
1668
+ assert.strictEqual(manager.getFailed().length, 0);
1669
+ assert.strictEqual(manager.capabilities.getAll(String).length, 1);
1670
+ }),
1671
+ );
1672
+ });
1673
+
1674
+ describe('plugin dependencies (dependsOn)', () => {
1675
+ // Build a small plugin with a `dependsOn` chain. The helper keeps each test
1676
+ // focused on the dependency semantics rather than module wiring.
1677
+ const makePlugin = (id: string, dependsOn?: string[], tags?: string[]) =>
1678
+ Plugin.make(Plugin.define({ id, name: id, dependsOn, tags }))();
1679
+
1680
+ it.effect('enable resolves the transitive closure in dependency-first order', () =>
1681
+ Effect.gen(function* () {
1682
+ const a = makePlugin('a');
1683
+ const b = makePlugin('b', ['a']);
1684
+ const c = makePlugin('c', ['b']);
1685
+ const manager = PluginManager.make({ plugins: [a, b, c], pluginLoader });
1686
+
1687
+ const ok = yield* manager.enable('c');
1688
+ assert.isTrue(ok);
1689
+ // `a` enables first, then `b`, then `c`.
1690
+ assert.deepStrictEqual(manager.getEnabled(), ['a', 'b', 'c']);
1691
+ }),
1692
+ );
1693
+
1694
+ it.effect('enable is idempotent when dependencies are already enabled', () =>
1695
+ Effect.gen(function* () {
1696
+ const a = makePlugin('a');
1697
+ const b = makePlugin('b', ['a']);
1698
+ const manager = PluginManager.make({ plugins: [a, b], pluginLoader });
1699
+
1700
+ yield* manager.enable('a');
1701
+ yield* manager.enable('b');
1702
+ assert.deepStrictEqual(manager.getEnabled(), ['a', 'b']);
1703
+ // Re-enabling shouldn't duplicate entries.
1704
+ yield* manager.enable('b');
1705
+ assert.deepStrictEqual(manager.getEnabled(), ['a', 'b']);
1706
+ }),
1707
+ );
1708
+
1709
+ it.effect('enable with a missing declared dependency records a PluginDependencyError', () =>
1710
+ Effect.gen(function* () {
1711
+ const dependent = makePlugin('dependent', ['org.dxos.missing']);
1712
+ const manager = PluginManager.make({ plugins: [dependent], pluginLoader });
1713
+
1714
+ const ok = yield* manager.enable('dependent');
1715
+ assert.isFalse(ok);
1716
+ assert.deepStrictEqual(manager.getEnabled(), []);
1717
+ const failures = manager.getFailed();
1718
+ assert.strictEqual(failures.length, 1);
1719
+ assert.strictEqual(failures[0].id, 'dependent');
1720
+ assert.instanceOf(failures[0].error, Plugin.PluginDependencyError);
1721
+ }),
1722
+ );
1723
+
1724
+ it.effect('enable detects A↔B cycle and records a cycle failure', () =>
1725
+ Effect.gen(function* () {
1726
+ const a = makePlugin('a', ['b']);
1727
+ const b = makePlugin('b', ['a']);
1728
+ const manager = PluginManager.make({ plugins: [a, b], pluginLoader });
1729
+
1730
+ const ok = yield* manager.enable('a');
1731
+ assert.isFalse(ok);
1732
+ assert.deepStrictEqual(manager.getEnabled(), []);
1733
+ const failures = manager.getFailed();
1734
+ assert.strictEqual(failures.length, 1);
1735
+ assert.instanceOf(failures[0].error, Plugin.PluginDependencyError);
1736
+ }),
1737
+ );
1738
+
1739
+ it.effect('enable with resolveDependencies: false skips closure walk and missing-dep check', () =>
1740
+ Effect.gen(function* () {
1741
+ // Dependent declares a dep that is intentionally unregistered — the
1742
+ // caller has accepted responsibility for satisfying it some other way.
1743
+ const dependent = makePlugin('dependent', ['org.dxos.alt-impl']);
1744
+ const manager = PluginManager.make({ plugins: [dependent], pluginLoader });
1745
+
1746
+ const ok = yield* manager.enable('dependent', { resolveDependencies: false });
1747
+ assert.isTrue(ok);
1748
+ assert.deepStrictEqual(manager.getEnabled(), ['dependent']);
1749
+ assert.strictEqual(manager.getFailed().length, 0);
1750
+ }),
1751
+ );
1752
+
1753
+ it.effect('disable cascades to transitive dependents by default', () =>
1754
+ Effect.gen(function* () {
1755
+ const a = makePlugin('a');
1756
+ const b = makePlugin('b', ['a']);
1757
+ const c = makePlugin('c', ['b']);
1758
+ const manager = PluginManager.make({ plugins: [a, b, c], pluginLoader });
1759
+
1760
+ yield* manager.enable('c');
1761
+ assert.deepStrictEqual(manager.getEnabled(), ['a', 'b', 'c']);
1762
+
1763
+ const ok = yield* manager.disable('a');
1764
+ assert.isTrue(ok);
1765
+ // Cascade tears down `c` (leaf) and `b` before `a`.
1766
+ assert.deepStrictEqual(manager.getEnabled(), []);
1767
+ }),
1768
+ );
1769
+
1770
+ it.effect('default disable refuses when a transitive dependent is core', () =>
1771
+ Effect.gen(function* () {
1772
+ const lib = makePlugin('lib');
1773
+ const coreClient = makePlugin('coreClient', ['lib'], ['system']);
1774
+ const manager = PluginManager.make({
1775
+ plugins: [lib, coreClient],
1776
+ enabled: ['lib'],
1777
+ pluginLoader,
1778
+ });
1779
+
1780
+ const exit = yield* Effect.exit(manager.disable('lib'));
1781
+ assert.isTrue(Exit.isFailure(exit));
1782
+ // No state mutation when cascade is refused for a core dependent.
1783
+ assert.isTrue(manager.getEnabled().includes('lib'));
1784
+ assert.isTrue(manager.getEnabled().includes('coreClient'));
1785
+ }),
1786
+ );
1787
+
1788
+ it.effect('disable with cascade: false disables only the target', () =>
1789
+ Effect.gen(function* () {
1790
+ const a = makePlugin('a');
1791
+ const b = makePlugin('b', ['a']);
1792
+ const manager = PluginManager.make({ plugins: [a, b], pluginLoader });
1793
+
1794
+ yield* manager.enable('b');
1795
+ const ok = yield* manager.disable('a', { cascade: false });
1796
+ assert.isTrue(ok);
1797
+ // `b` is left enabled-but-broken (no `a` to satisfy its declared dep).
1798
+ assert.deepStrictEqual(manager.getEnabled(), ['b']);
1799
+ }),
1800
+ );
1801
+
1802
+ it.effect('getDependencies and getDependents reflect the declared graph', () =>
1803
+ Effect.gen(function* () {
1804
+ const a = makePlugin('a');
1805
+ const b = makePlugin('b', ['a']);
1806
+ const c = makePlugin('c', ['b']);
1807
+ const manager = PluginManager.make({ plugins: [a, b, c], pluginLoader });
1808
+
1809
+ assert.deepStrictEqual([...manager.getDependencies('c', { transitive: false })], ['b']);
1810
+ assert.deepStrictEqual([...manager.getDependencies('c', { transitive: true })], ['a', 'b']);
1811
+
1812
+ assert.deepStrictEqual([...manager.getDependents('a', { transitive: false })], ['b']);
1813
+ assert.deepStrictEqual([...manager.getDependents('a', { transitive: true })], ['c', 'b']);
1814
+
1815
+ yield* manager.enable('b');
1816
+ assert.deepStrictEqual([...manager.getDependents('a', { transitive: true, enabledOnly: true })], ['b']);
1817
+ }),
1818
+ );
1819
+
1820
+ it.effect('enable installs a catalog-only dependency via add() before enabling it', () =>
1821
+ Effect.gen(function* () {
1822
+ // The "remote" plugin is not pre-registered; the loader knows about
1823
+ // it, simulating a fetch from the registry.
1824
+ const remote = makePlugin('remote');
1825
+ const dependent = makePlugin('dependent', ['remote']);
1826
+ const remoteLoader = Effect.fn(function* (id: string) {
1827
+ if (id === 'remote') {
1828
+ return { plugin: remote };
1829
+ }
1830
+ throw new Error(`Unknown id: ${id}`);
1831
+ });
1832
+
1833
+ const registry = Registry.make();
1834
+ const manager = PluginManager.make({
1835
+ plugins: [dependent],
1836
+ pluginLoader: remoteLoader,
1837
+ registry,
1838
+ });
1839
+ // Seed the catalog with the remote entry so the dependency walk can
1840
+ // discover it.
1841
+ registry.set(manager.pluginRegistry.plugins, {
1842
+ entries: [
1843
+ {
1844
+ id: 'remote',
1845
+ name: 'Remote',
1846
+ moduleUrl: 'about:blank',
1847
+ repo: 'example/remote',
1848
+ version: 'v0.0.0',
1849
+ },
1850
+ ],
1851
+ loading: false,
1852
+ error: null,
1853
+ });
1854
+
1855
+ const ok = yield* manager.enable('dependent');
1856
+ assert.isTrue(ok);
1857
+ assert.deepStrictEqual(manager.getEnabled(), ['remote', 'dependent']);
1858
+ assert.isTrue(manager.getPlugins().some((plugin) => plugin.meta.id === 'remote'));
1859
+ }),
1860
+ );
1861
+
1862
+ it.effect('enable records install-failed when a catalog-only dep fails to load', () =>
1863
+ Effect.gen(function* () {
1864
+ const dependent = makePlugin('dependent', ['remote-broken']);
1865
+ const failingLoader = Effect.fn(function* (_id: string) {
1866
+ return yield* Effect.fail(new Error('fetch failed'));
1867
+ });
1868
+
1869
+ const registry = Registry.make();
1870
+ const manager = PluginManager.make({
1871
+ plugins: [dependent],
1872
+ pluginLoader: failingLoader,
1873
+ registry,
1874
+ });
1875
+ registry.set(manager.pluginRegistry.plugins, {
1876
+ entries: [
1877
+ {
1878
+ id: 'remote-broken',
1879
+ name: 'Broken',
1880
+ moduleUrl: 'about:blank',
1881
+ repo: 'example/broken',
1882
+ version: 'v0.0.0',
1883
+ },
1884
+ ],
1885
+ loading: false,
1886
+ error: null,
1887
+ });
1888
+
1889
+ const ok = yield* manager.enable('dependent');
1890
+ assert.isFalse(ok);
1891
+ assert.deepStrictEqual(manager.getEnabled(), []);
1892
+ const failures = manager.getFailed();
1893
+ assert.strictEqual(failures.length, 1);
1894
+ assert.strictEqual(failures[0].id, 'dependent');
1895
+ assert.instanceOf(failures[0].error, Plugin.PluginDependencyError);
1896
+ }),
1897
+ );
1898
+ });
845
1899
  });