@dxos/app-framework 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef

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