@dxos/app-framework 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934

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 (307) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/{capability-OP63CD5N.mjs → capability-HRFPP52W.mjs} +11 -11
  4. package/dist/lib/browser/capability-HRFPP52W.mjs.map +7 -0
  5. package/dist/lib/browser/{capability-BBBBAPDI.mjs → capability-VQ236TY5.mjs} +10 -10
  6. package/dist/lib/browser/{chunk-T3Y4AEKX.mjs → chunk-23D4SJUE.mjs} +3 -3
  7. package/dist/lib/browser/{chunk-T3Y4AEKX.mjs.map → chunk-23D4SJUE.mjs.map} +1 -1
  8. package/dist/lib/browser/{chunk-2CKCJ6PN.mjs → chunk-3JWJXGLK.mjs} +1 -1
  9. package/dist/lib/browser/{chunk-2CKCJ6PN.mjs.map → chunk-3JWJXGLK.mjs.map} +1 -1
  10. package/dist/lib/browser/{chunk-I34GF4NG.mjs → chunk-45CHLTBV.mjs} +2 -2
  11. package/dist/lib/browser/{chunk-QSXYHXCE.mjs → chunk-66IXTIVK.mjs} +1 -1
  12. package/dist/lib/browser/{chunk-QSXYHXCE.mjs.map → chunk-66IXTIVK.mjs.map} +2 -2
  13. package/dist/lib/browser/chunk-6GZYCP73.mjs +422 -0
  14. package/dist/lib/browser/chunk-6GZYCP73.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-7VZJR2OA.mjs +581 -0
  16. package/dist/lib/browser/chunk-7VZJR2OA.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-TGX63LTL.mjs → chunk-FJ4765WW.mjs} +1 -1
  18. package/dist/lib/browser/{chunk-TGX63LTL.mjs.map → chunk-FJ4765WW.mjs.map} +2 -2
  19. package/dist/lib/browser/chunk-FO3IYSLV.mjs +68 -0
  20. package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +7 -0
  21. package/dist/lib/browser/{chunk-FU4GAFUQ.mjs → chunk-G6Q2M2M5.mjs} +85 -15
  22. package/dist/lib/browser/chunk-G6Q2M2M5.mjs.map +7 -0
  23. package/dist/lib/browser/chunk-J43JDWAV.mjs +1445 -0
  24. package/dist/lib/browser/chunk-J43JDWAV.mjs.map +7 -0
  25. package/dist/lib/browser/{chunk-JKWMHZP6.mjs → chunk-WBHCSOBW.mjs} +2 -2
  26. package/dist/lib/browser/chunk-WBHCSOBW.mjs.map +7 -0
  27. package/dist/lib/browser/chunk-XLYULXAO.mjs +469 -0
  28. package/dist/lib/browser/chunk-XLYULXAO.mjs.map +7 -0
  29. package/dist/lib/browser/{chunk-F7FW2RK2.mjs → chunk-ZGJAZSNE.mjs} +7 -32
  30. package/dist/lib/browser/chunk-ZGJAZSNE.mjs.map +7 -0
  31. package/dist/lib/browser/cli/index.mjs +14 -28
  32. package/dist/lib/browser/cli/index.mjs.map +3 -3
  33. package/dist/lib/browser/common/activation-events.mjs +7 -7
  34. package/dist/lib/browser/common/capabilities.mjs +7 -7
  35. package/dist/lib/browser/core/activation-event.mjs +1 -1
  36. package/dist/lib/browser/core/capability.mjs +1 -1
  37. package/dist/lib/browser/core/plugin-manager.mjs +8 -4
  38. package/dist/lib/browser/core/plugin.mjs +12 -2
  39. package/dist/lib/browser/core/url-loader.mjs +17 -5
  40. package/dist/lib/browser/index.mjs +38 -21
  41. package/dist/lib/browser/index.mjs.map +3 -3
  42. package/dist/lib/browser/{invoker-capability-H5PPENOC.mjs → invoker-capability-CEL2L6GX.mjs} +12 -11
  43. package/dist/lib/browser/invoker-capability-CEL2L6GX.mjs.map +7 -0
  44. package/dist/lib/browser/meta.json +1 -1
  45. package/dist/lib/browser/testing/index.mjs +149 -28
  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 +18 -14
  50. package/dist/lib/node-esm/{capability-AWBEMRYR.mjs → capability-GS3XJH74.mjs} +10 -10
  51. package/dist/lib/node-esm/{capability-WFEG6CIZ.mjs → capability-I5P7ACFL.mjs} +11 -11
  52. package/dist/lib/node-esm/capability-I5P7ACFL.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-FKE4Z3D6.mjs → chunk-37Z53PXZ.mjs} +1 -1
  54. package/dist/lib/node-esm/{chunk-FKE4Z3D6.mjs.map → chunk-37Z53PXZ.mjs.map} +2 -2
  55. package/dist/lib/node-esm/chunk-4RJL37GN.mjs +423 -0
  56. package/dist/lib/node-esm/chunk-4RJL37GN.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs → chunk-6XW6LET6.mjs} +2 -2
  58. package/dist/lib/node-esm/{chunk-URWHJQT2.mjs → chunk-D347W3KO.mjs} +7 -32
  59. package/dist/lib/node-esm/chunk-D347W3KO.mjs.map +7 -0
  60. package/dist/lib/node-esm/chunk-DW45NFLW.mjs +1446 -0
  61. package/dist/lib/node-esm/chunk-DW45NFLW.mjs.map +7 -0
  62. package/dist/lib/node-esm/chunk-EI4LYXFY.mjs +470 -0
  63. package/dist/lib/node-esm/chunk-EI4LYXFY.mjs.map +7 -0
  64. package/dist/lib/node-esm/{chunk-ULUEXB7Q.mjs → chunk-KIFOQXD4.mjs} +85 -15
  65. package/dist/lib/node-esm/chunk-KIFOQXD4.mjs.map +7 -0
  66. package/dist/lib/node-esm/{chunk-VKHGNEDB.mjs → chunk-SBS2YMPT.mjs} +3 -3
  67. package/dist/lib/node-esm/{chunk-VKHGNEDB.mjs.map → chunk-SBS2YMPT.mjs.map} +1 -1
  68. package/dist/lib/node-esm/{chunk-42KBWDE4.mjs → chunk-SDJ4B2LU.mjs} +1 -1
  69. package/dist/lib/node-esm/{chunk-42KBWDE4.mjs.map → chunk-SDJ4B2LU.mjs.map} +1 -1
  70. package/dist/lib/node-esm/chunk-V24UWT36.mjs +582 -0
  71. package/dist/lib/node-esm/chunk-V24UWT36.mjs.map +7 -0
  72. package/dist/lib/node-esm/{chunk-G3RTFSNG.mjs → chunk-WFSRZKBP.mjs} +2 -2
  73. package/dist/lib/node-esm/chunk-WFSRZKBP.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-ZZ7CKK6W.mjs → chunk-XOCUANHO.mjs} +1 -1
  77. package/dist/lib/node-esm/{chunk-ZZ7CKK6W.mjs.map → chunk-XOCUANHO.mjs.map} +2 -2
  78. package/dist/lib/node-esm/cli/index.mjs +14 -28
  79. package/dist/lib/node-esm/cli/index.mjs.map +3 -3
  80. package/dist/lib/node-esm/common/activation-events.mjs +7 -7
  81. package/dist/lib/node-esm/common/capabilities.mjs +7 -7
  82. package/dist/lib/node-esm/core/activation-event.mjs +1 -1
  83. package/dist/lib/node-esm/core/capability.mjs +1 -1
  84. package/dist/lib/node-esm/core/plugin-manager.mjs +8 -4
  85. package/dist/lib/node-esm/core/plugin.mjs +12 -2
  86. package/dist/lib/node-esm/core/url-loader.mjs +17 -5
  87. package/dist/lib/node-esm/index.mjs +38 -21
  88. package/dist/lib/node-esm/index.mjs.map +3 -3
  89. package/dist/lib/node-esm/{invoker-capability-S3ZA527J.mjs → invoker-capability-QKUKN2MF.mjs} +12 -11
  90. package/dist/lib/node-esm/invoker-capability-QKUKN2MF.mjs.map +7 -0
  91. package/dist/lib/node-esm/meta.json +1 -1
  92. package/dist/lib/node-esm/testing/index.mjs +149 -28
  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 +18 -14
  97. package/dist/plugin/node-esm/index.mjs +542 -33
  98. package/dist/plugin/node-esm/index.mjs.map +4 -4
  99. package/dist/plugin/node-esm/meta.json +1 -1
  100. package/dist/types/src/common/annotations.d.ts +1 -0
  101. package/dist/types/src/common/annotations.d.ts.map +1 -0
  102. package/dist/types/src/common/capabilities.d.ts +2 -1
  103. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  104. package/dist/types/src/common/operations.d.ts +1 -1
  105. package/dist/types/src/common/operations.d.ts.map +1 -1
  106. package/dist/types/src/core/activation-event.d.ts +4 -4
  107. package/dist/types/src/core/activation-event.d.ts.map +1 -1
  108. package/dist/types/src/core/capability-manager.d.ts.map +1 -1
  109. package/dist/types/src/core/capability.d.ts +2 -2
  110. package/dist/types/src/core/capability.d.ts.map +1 -1
  111. package/dist/types/src/core/edge-registry-plugin-provider.d.ts +30 -0
  112. package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -0
  113. package/dist/types/src/core/index.d.ts +5 -0
  114. package/dist/types/src/core/index.d.ts.map +1 -1
  115. package/dist/types/src/core/plugin-asset-cache.d.ts +71 -0
  116. package/dist/types/src/core/plugin-asset-cache.d.ts.map +1 -0
  117. package/dist/types/src/core/plugin-manager.d.ts +230 -5
  118. package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
  119. package/dist/types/src/core/plugin-manifest.d.ts +101 -0
  120. package/dist/types/src/core/plugin-manifest.d.ts.map +1 -0
  121. package/dist/types/src/core/plugin-manifest.test.d.ts +2 -0
  122. package/dist/types/src/core/plugin-manifest.test.d.ts.map +1 -0
  123. package/dist/types/src/core/plugin.d.ts +164 -6
  124. package/dist/types/src/core/plugin.d.ts.map +1 -1
  125. package/dist/types/src/core/registry.d.ts +107 -0
  126. package/dist/types/src/core/registry.d.ts.map +1 -0
  127. package/dist/types/src/core/url-loader.d.ts +105 -3
  128. package/dist/types/src/core/url-loader.d.ts.map +1 -1
  129. package/dist/types/src/helpers.d.ts.map +1 -1
  130. package/dist/types/src/plugin-operation/history/capability.d.ts.map +1 -1
  131. package/dist/types/src/plugin-operation/history/errors.d.ts +6 -6
  132. package/dist/types/src/plugin-operation/history/errors.d.ts.map +1 -1
  133. package/dist/types/src/plugin-operation/history/history-tracker.d.ts +1 -1
  134. package/dist/types/src/plugin-operation/history/history-tracker.d.ts.map +1 -1
  135. package/dist/types/src/plugin-operation/history/types.d.ts +1 -1
  136. package/dist/types/src/plugin-operation/history/types.d.ts.map +1 -1
  137. package/dist/types/src/plugin-operation/history/undo-mapping.d.ts +1 -1
  138. package/dist/types/src/plugin-operation/history/undo-mapping.d.ts.map +1 -1
  139. package/dist/types/src/plugin-operation/history/undo-registry.d.ts +1 -1
  140. package/dist/types/src/plugin-operation/history/undo-registry.d.ts.map +1 -1
  141. package/dist/types/src/plugin-operation/invoker-capability.d.ts +1 -1
  142. package/dist/types/src/plugin-operation/invoker-capability.d.ts.map +1 -1
  143. package/dist/types/src/plugin-operation/meta.d.ts.map +1 -1
  144. package/dist/types/src/plugin-operation/testing.d.ts +2 -1
  145. package/dist/types/src/plugin-operation/testing.d.ts.map +1 -1
  146. package/dist/types/src/plugin-runtime/capability.d.ts +1 -1
  147. package/dist/types/src/plugin-runtime/capability.d.ts.map +1 -1
  148. package/dist/types/src/plugin-runtime/meta.d.ts.map +1 -1
  149. package/dist/types/src/testing/harness.d.ts +67 -0
  150. package/dist/types/src/testing/harness.d.ts.map +1 -0
  151. package/dist/types/src/testing/index.d.ts +1 -0
  152. package/dist/types/src/testing/index.d.ts.map +1 -1
  153. package/dist/types/src/testing/react.d.ts +27 -0
  154. package/dist/types/src/testing/react.d.ts.map +1 -0
  155. package/dist/types/src/testing/react.test.d.ts +2 -0
  156. package/dist/types/src/testing/react.test.d.ts.map +1 -0
  157. package/dist/types/src/testing/service.d.ts.map +1 -1
  158. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  159. package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
  160. package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
  161. package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -1
  162. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts +64 -0
  163. package/dist/types/src/ui/components/Placeholder/Placeholder.d.ts.map +1 -0
  164. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts +19 -0
  165. package/dist/types/src/ui/components/Placeholder/Placeholder.stories.d.ts.map +1 -0
  166. package/dist/types/src/ui/components/Placeholder/index.d.ts +2 -0
  167. package/dist/types/src/ui/components/Placeholder/index.d.ts.map +1 -0
  168. package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts.map +1 -1
  169. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +16 -4
  170. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
  171. package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
  172. package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +1 -1
  173. package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -1
  174. package/dist/types/src/ui/components/Surface/index.d.ts +16 -6
  175. package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
  176. package/dist/types/src/ui/components/Surface/types.d.ts +110 -9
  177. package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
  178. package/dist/types/src/ui/components/Surface/types.test.d.ts +2 -0
  179. package/dist/types/src/ui/components/Surface/types.test.d.ts.map +1 -0
  180. package/dist/types/src/ui/components/index.d.ts +1 -0
  181. package/dist/types/src/ui/components/index.d.ts.map +1 -1
  182. package/dist/types/src/ui/hooks/useApp.d.ts +30 -3
  183. package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
  184. package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
  185. package/dist/types/src/ui/hooks/useLoading.d.ts.map +1 -1
  186. package/dist/types/src/ui/hooks/useSettingsState.d.ts.map +1 -1
  187. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts +34 -0
  188. package/dist/types/src/vite-plugin/boot-loader/BootLoader.stories.d.ts.map +1 -0
  189. package/dist/types/src/vite-plugin/boot-loader/index.d.ts +2 -0
  190. package/dist/types/src/vite-plugin/boot-loader/index.d.ts.map +1 -0
  191. package/dist/types/src/vite-plugin/boot-loader/loader.d.ts +51 -0
  192. package/dist/types/src/vite-plugin/boot-loader/loader.d.ts.map +1 -0
  193. package/dist/types/src/vite-plugin/composer/index.d.ts +34 -0
  194. package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -0
  195. package/dist/types/src/vite-plugin/import-map/index.d.ts +28 -0
  196. package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -0
  197. package/dist/types/src/vite-plugin/index.d.ts +4 -2
  198. package/dist/types/src/vite-plugin/index.d.ts.map +1 -1
  199. package/dist/types/src/vite-plugin/manifest.d.ts +41 -0
  200. package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -0
  201. package/dist/types/src/vite-plugin/manifest.test.d.ts +2 -0
  202. package/dist/types/src/vite-plugin/manifest.test.d.ts.map +1 -0
  203. package/dist/types/src/vite-plugin/packages.d.ts +10 -4
  204. package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
  205. package/dist/types/tsconfig.tsbuildinfo +1 -1
  206. package/moon.yml +1 -0
  207. package/package.json +36 -60
  208. package/src/cli/cli.ts +1 -1
  209. package/src/common/annotations.ts +3 -0
  210. package/src/common/capabilities.ts +2 -1
  211. package/src/common/operations.ts +1 -1
  212. package/src/core/capability.ts +1 -1
  213. package/src/core/edge-registry-plugin-provider.ts +92 -0
  214. package/src/core/index.ts +5 -0
  215. package/src/core/plugin-asset-cache.ts +60 -0
  216. package/src/core/plugin-manager.test.ts +793 -7
  217. package/src/core/plugin-manager.ts +846 -44
  218. package/src/core/plugin-manifest.test.ts +75 -0
  219. package/src/core/plugin-manifest.ts +134 -0
  220. package/src/core/plugin.ts +170 -10
  221. package/src/core/registry.ts +163 -0
  222. package/src/core/url-loader.test.ts +148 -6
  223. package/src/core/url-loader.ts +284 -44
  224. package/src/plugin-operation/OperationPlugin.ts +2 -2
  225. package/src/plugin-operation/history/capability.ts +1 -1
  226. package/src/plugin-operation/history/history-tracker.test.ts +2 -1
  227. package/src/plugin-operation/history/history-tracker.ts +1 -1
  228. package/src/plugin-operation/history/types.ts +1 -1
  229. package/src/plugin-operation/history/undo-mapping.ts +1 -1
  230. package/src/plugin-operation/history/undo-registry.ts +1 -1
  231. package/src/plugin-operation/invoker-capability.ts +2 -1
  232. package/src/plugin-operation/meta.ts +1 -0
  233. package/src/plugin-operation/testing.ts +2 -1
  234. package/src/plugin-runtime/RuntimePlugin.ts +2 -2
  235. package/src/plugin-runtime/meta.ts +1 -0
  236. package/src/testing/harness.ts +229 -0
  237. package/src/testing/index.ts +1 -0
  238. package/src/testing/react.test.tsx +48 -0
  239. package/src/testing/react.tsx +113 -0
  240. package/src/testing/service.ts +1 -1
  241. package/src/testing/withPluginManager.stories.tsx +1 -1
  242. package/src/testing/withPluginManager.tsx +0 -2
  243. package/src/ui/components/App/App.stories.tsx +2 -6
  244. package/src/ui/components/App/App.tsx +25 -2
  245. package/src/ui/components/Placeholder/Placeholder.stories.tsx +77 -0
  246. package/src/ui/components/Placeholder/Placeholder.tsx +155 -0
  247. package/src/ui/components/Placeholder/index.ts +5 -0
  248. package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +4 -2
  249. package/src/ui/components/Surface/SurfaceComponent.stories.tsx +1 -1
  250. package/src/ui/components/Surface/SurfaceComponent.tsx +85 -48
  251. package/src/ui/components/Surface/SurfaceInfo.tsx +0 -1
  252. package/src/ui/components/Surface/index.ts +20 -1
  253. package/src/ui/components/Surface/types.test.ts +126 -0
  254. package/src/ui/components/Surface/types.ts +164 -12
  255. package/src/ui/components/index.ts +1 -0
  256. package/src/ui/hooks/useApp.test.tsx +1 -1
  257. package/src/ui/hooks/useApp.tsx +177 -42
  258. package/src/ui/hooks/useLoading.tsx +14 -6
  259. package/src/vite-plugin/boot-loader/BootLoader.stories.tsx +270 -0
  260. package/src/vite-plugin/boot-loader/boot-loader.css +321 -0
  261. package/src/vite-plugin/boot-loader/boot-loader.js +325 -0
  262. package/src/vite-plugin/boot-loader/index.ts +5 -0
  263. package/src/vite-plugin/boot-loader/loader.ts +123 -0
  264. package/src/vite-plugin/composer/index.ts +306 -0
  265. package/src/vite-plugin/import-map/index.ts +527 -0
  266. package/src/vite-plugin/index.ts +6 -2
  267. package/src/vite-plugin/manifest.test.ts +46 -0
  268. package/src/vite-plugin/manifest.ts +57 -0
  269. package/src/vite-plugin/packages.ts +168 -10
  270. package/tsconfig.json +16 -1
  271. package/.swc/plugins/linux_x86_64_19.0.0/727453fb3a62f7f1d952a41e051ca8a6f88cadc45cee43c6a4d1aa45f9b75665.wasmer-v7 +0 -0
  272. package/dist/lib/browser/capability-OP63CD5N.mjs.map +0 -7
  273. package/dist/lib/browser/chunk-F7FW2RK2.mjs.map +0 -7
  274. package/dist/lib/browser/chunk-FU4GAFUQ.mjs.map +0 -7
  275. package/dist/lib/browser/chunk-GX4TUNM6.mjs +0 -963
  276. package/dist/lib/browser/chunk-GX4TUNM6.mjs.map +0 -7
  277. package/dist/lib/browser/chunk-JKWMHZP6.mjs.map +0 -7
  278. package/dist/lib/browser/chunk-LVJW5EFU.mjs +0 -157
  279. package/dist/lib/browser/chunk-LVJW5EFU.mjs.map +0 -7
  280. package/dist/lib/browser/chunk-RFSO3JRG.mjs +0 -1
  281. package/dist/lib/browser/chunk-RFSO3JRG.mjs.map +0 -7
  282. package/dist/lib/browser/chunk-WPE6AL7I.mjs +0 -905
  283. package/dist/lib/browser/chunk-WPE6AL7I.mjs.map +0 -7
  284. package/dist/lib/browser/invoker-capability-H5PPENOC.mjs.map +0 -7
  285. package/dist/lib/node-esm/capability-WFEG6CIZ.mjs.map +0 -7
  286. package/dist/lib/node-esm/chunk-4A3ZCMI3.mjs +0 -158
  287. package/dist/lib/node-esm/chunk-4A3ZCMI3.mjs.map +0 -7
  288. package/dist/lib/node-esm/chunk-BCEOLX47.mjs +0 -964
  289. package/dist/lib/node-esm/chunk-BCEOLX47.mjs.map +0 -7
  290. package/dist/lib/node-esm/chunk-EL3R25OQ.mjs +0 -2
  291. package/dist/lib/node-esm/chunk-EL3R25OQ.mjs.map +0 -7
  292. package/dist/lib/node-esm/chunk-G3RTFSNG.mjs.map +0 -7
  293. package/dist/lib/node-esm/chunk-LQKOTNJW.mjs +0 -906
  294. package/dist/lib/node-esm/chunk-LQKOTNJW.mjs.map +0 -7
  295. package/dist/lib/node-esm/chunk-ULUEXB7Q.mjs.map +0 -7
  296. package/dist/lib/node-esm/chunk-URWHJQT2.mjs.map +0 -7
  297. package/dist/lib/node-esm/invoker-capability-S3ZA527J.mjs.map +0 -7
  298. package/dist/types/src/vite-plugin/composer-plugin.d.ts +0 -18
  299. package/dist/types/src/vite-plugin/composer-plugin.d.ts.map +0 -1
  300. package/dist/types/src/vite-plugin/import-map-plugin.d.ts +0 -16
  301. package/dist/types/src/vite-plugin/import-map-plugin.d.ts.map +0 -1
  302. package/src/vite-plugin/composer-plugin.ts +0 -128
  303. package/src/vite-plugin/import-map-plugin.ts +0 -314
  304. /package/dist/lib/browser/{capability-BBBBAPDI.mjs.map → capability-VQ236TY5.mjs.map} +0 -0
  305. /package/dist/lib/browser/{chunk-I34GF4NG.mjs.map → chunk-45CHLTBV.mjs.map} +0 -0
  306. /package/dist/lib/node-esm/{capability-AWBEMRYR.mjs.map → capability-GS3XJH74.mjs.map} +0 -0
  307. /package/dist/lib/node-esm/{chunk-WZCSOX5Q.mjs.map → chunk-6XW6LET6.mjs.map} +0 -0
@@ -5,19 +5,133 @@
5
5
  import { assert, describe, it } from '@effect/vitest';
6
6
  import * as Effect from 'effect/Effect';
7
7
 
8
+ import { runAndForwardErrors } from '@dxos/effect';
9
+
8
10
  import * as Plugin from './plugin';
11
+ import * as PluginAssetCache from './plugin-asset-cache';
9
12
  import * as UrlLoader from './url-loader';
10
13
 
11
14
  const testMeta = { id: 'org.dxos.plugin.test', name: 'Test' };
12
15
 
16
+ const memoryStorage = (initial: string | null = null): UrlLoader.Storage => {
17
+ let value = initial;
18
+ return {
19
+ get: () => value,
20
+ set: (_key, next) => {
21
+ value = next;
22
+ },
23
+ };
24
+ };
25
+
26
+ type CacheCall = { method: 'cache' | 'evict' | 'resolve'; pluginId: string; urls?: readonly string[]; url?: string };
27
+
28
+ const recordingCache = (): { cache: PluginAssetCache.Cache; calls: CacheCall[] } => {
29
+ const calls: CacheCall[] = [];
30
+ return {
31
+ calls,
32
+ cache: {
33
+ cache: (pluginId, urls) =>
34
+ Effect.sync(() => {
35
+ calls.push({ method: 'cache', pluginId, urls });
36
+ }),
37
+ evict: (pluginId) =>
38
+ Effect.sync(() => {
39
+ calls.push({ method: 'evict', pluginId });
40
+ }),
41
+ resolve: (pluginId, url) =>
42
+ Effect.sync(() => {
43
+ calls.push({ method: 'resolve', pluginId, url });
44
+ return url;
45
+ }),
46
+ list: () => Effect.succeed([] as readonly string[]),
47
+ },
48
+ };
49
+ };
50
+
13
51
  describe('UrlLoader', () => {
52
+ describe('isLocalUrl', () => {
53
+ it('matches localhost, 127.0.0.1, and ::1', ({ expect }) => {
54
+ expect(UrlLoader.isLocalUrl('http://localhost:5173/plugin.mjs')).toBe(true);
55
+ expect(UrlLoader.isLocalUrl('https://LOCALHOST/plugin.mjs')).toBe(true);
56
+ expect(UrlLoader.isLocalUrl('http://127.0.0.1:8080/plugin.mjs')).toBe(true);
57
+ expect(UrlLoader.isLocalUrl('http://[::1]:8080/plugin.mjs')).toBe(true);
58
+ });
59
+
60
+ it('rejects public and malformed URLs', ({ expect }) => {
61
+ expect(UrlLoader.isLocalUrl('https://example.com/plugin.mjs')).toBe(false);
62
+ expect(UrlLoader.isLocalUrl('https://192.168.1.10/plugin.mjs')).toBe(false);
63
+ expect(UrlLoader.isLocalUrl('not a url')).toBe(false);
64
+ expect(UrlLoader.isLocalUrl('')).toBe(false);
65
+ });
66
+ });
67
+
68
+ describe('getRemoteEntries', () => {
69
+ it('returns persisted entries from storage', ({ expect }) => {
70
+ const storage: UrlLoader.Storage = {
71
+ get: () => '[{"id":"p1","url":"http://localhost:5173/p.mjs"}]',
72
+ set: () => {},
73
+ };
74
+ expect(UrlLoader.getRemoteEntries({ storage })).toEqual([{ id: 'p1', url: 'http://localhost:5173/p.mjs' }]);
75
+ });
76
+
77
+ it('returns an empty array when storage is empty or malformed', ({ expect }) => {
78
+ const empty: UrlLoader.Storage = { get: () => null, set: () => {} };
79
+ const malformed: UrlLoader.Storage = { get: () => '{not json', set: () => {} };
80
+ expect(UrlLoader.getRemoteEntries({ storage: empty })).toEqual([]);
81
+ expect(UrlLoader.getRemoteEntries({ storage: malformed })).toEqual([]);
82
+ });
83
+ });
84
+
85
+ describe('setInstalledVersion / getInstalledVersion', () => {
86
+ it('stores and retrieves an installed version', ({ expect }) => {
87
+ const stored: Record<string, string> = {};
88
+ const storage: UrlLoader.Storage = {
89
+ get: (key) => stored[key] ?? null,
90
+ set: (key, value) => {
91
+ stored[key] = value;
92
+ },
93
+ };
94
+
95
+ // Seed a remote entry without a version.
96
+ storage.set('test-key', JSON.stringify([{ id: 'org.dxos.plugin.foo', url: 'http://example.com/p.mjs' }]));
97
+
98
+ UrlLoader.setInstalledVersion('org.dxos.plugin.foo', 'v1.0.0', { storage, key: 'test-key' });
99
+
100
+ expect(UrlLoader.getInstalledVersion('org.dxos.plugin.foo', { storage, key: 'test-key' })).toBe('v1.0.0');
101
+ });
102
+
103
+ it('returns undefined when plugin is not persisted', ({ expect }) => {
104
+ const storage: UrlLoader.Storage = { get: () => null, set: () => {} };
105
+ expect(UrlLoader.getInstalledVersion('org.dxos.plugin.missing', { storage })).toBeUndefined();
106
+ });
107
+
108
+ it('overwrites an existing version when called again', ({ expect }) => {
109
+ const stored: Record<string, string> = {};
110
+ const storage: UrlLoader.Storage = {
111
+ get: (key) => stored[key] ?? null,
112
+ set: (key, value) => {
113
+ stored[key] = value;
114
+ },
115
+ };
116
+ storage.set(
117
+ 'test-key',
118
+ JSON.stringify([{ id: 'org.dxos.plugin.foo', url: 'http://example.com/p.mjs', version: 'v1.0.0' }]),
119
+ );
120
+
121
+ UrlLoader.setInstalledVersion('org.dxos.plugin.foo', 'v2.0.0', { storage, key: 'test-key' });
122
+ expect(UrlLoader.getInstalledVersion('org.dxos.plugin.foo', { storage, key: 'test-key' })).toBe('v2.0.0');
123
+ });
124
+ });
125
+
14
126
  describe('make', () => {
15
127
  it.effect('resolves built-in plugins by meta.id', () =>
16
128
  Effect.gen(function* () {
17
129
  const testPlugin = Plugin.make(Plugin.define(testMeta))();
18
130
  const loader = UrlLoader.make([testPlugin]);
19
131
  const result = yield* loader(testMeta.id);
20
- assert.strictEqual(result.meta.id, testMeta.id);
132
+ assert.strictEqual(result.plugin.meta.id, testMeta.id);
133
+ // Builtins are not dev plugins.
134
+ assert.notOk(result.dev);
21
135
  }),
22
136
  );
23
137
 
@@ -36,7 +150,7 @@ describe('UrlLoader', () => {
36
150
  get: () => null,
37
151
  set: () => {},
38
152
  };
39
- const result = await UrlLoader.preload({ storage });
153
+ const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
40
154
  expect(result).toEqual([]);
41
155
  });
42
156
 
@@ -45,7 +159,7 @@ describe('UrlLoader', () => {
45
159
  get: () => '{{invalid json',
46
160
  set: () => {},
47
161
  };
48
- const result = await UrlLoader.preload({ storage });
162
+ const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
49
163
  expect(result).toEqual([]);
50
164
  });
51
165
 
@@ -54,7 +168,7 @@ describe('UrlLoader', () => {
54
168
  get: () => 'null',
55
169
  set: () => {},
56
170
  };
57
- const result = await UrlLoader.preload({ storage });
171
+ const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
58
172
  expect(result).toEqual([]);
59
173
  });
60
174
 
@@ -63,7 +177,7 @@ describe('UrlLoader', () => {
63
177
  get: () => '{}',
64
178
  set: () => {},
65
179
  };
66
- const result = await UrlLoader.preload({ storage });
180
+ const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
67
181
  expect(result).toEqual([]);
68
182
  });
69
183
 
@@ -72,8 +186,36 @@ describe('UrlLoader', () => {
72
186
  get: () => '[{"title":"no url"}]',
73
187
  set: () => {},
74
188
  };
75
- const result = await UrlLoader.preload({ storage });
189
+ const result = await runAndForwardErrors(UrlLoader.preload({ storage }));
76
190
  expect(result).toEqual([]);
77
191
  });
78
192
  });
193
+
194
+ describe('uninstall', () => {
195
+ it('removes the persisted entry and evicts cached assets', async ({ expect }) => {
196
+ const storage = memoryStorage('[{"id":"p1","url":"https://x/p1.json"},{"id":"p2","url":"https://x/p2.json"}]');
197
+ const { cache, calls } = recordingCache();
198
+ await runAndForwardErrors(UrlLoader.uninstall('p1', { storage, cache }));
199
+ expect(UrlLoader.getRemoteEntries({ storage })).toEqual([{ id: 'p2', url: 'https://x/p2.json' }]);
200
+ expect(calls).toEqual([{ method: 'evict', pluginId: 'p1' }]);
201
+ });
202
+
203
+ it('still removes entry when cache eviction fails', async ({ expect }) => {
204
+ const storage = memoryStorage('[{"id":"p1","url":"https://x/p1.json"}]');
205
+ const cache: PluginAssetCache.Cache = {
206
+ cache: () => Effect.void,
207
+ evict: () =>
208
+ Effect.fail(
209
+ new PluginAssetCache.PluginAssetCacheError({
210
+ context: { operation: 'evict', pluginId: 'p1' },
211
+ cause: 'boom',
212
+ }),
213
+ ),
214
+ resolve: (_id, url) => Effect.succeed(url),
215
+ list: () => Effect.succeed([] as readonly string[]),
216
+ };
217
+ await runAndForwardErrors(UrlLoader.uninstall('p1', { storage, cache }));
218
+ expect(UrlLoader.getRemoteEntries({ storage })).toEqual([]);
219
+ });
220
+ });
79
221
  });
@@ -3,13 +3,25 @@
3
3
  //
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
+ import * as Option from 'effect/Option';
6
7
 
8
+ import { BaseError } from '@dxos/errors';
7
9
  import { log } from '@dxos/log';
8
10
 
9
11
  import * as Plugin from './plugin';
12
+ import * as PluginAssetCache from './plugin-asset-cache';
13
+ import * as PluginManifest from './plugin-manifest';
10
14
 
11
15
  const DEFAULT_KEY = 'org.dxos.composer.remote-plugins';
12
16
 
17
+ /**
18
+ * Tagged error for any failure during remote plugin loading. Construction sites
19
+ * set `context.locator` and `context.reason` (one of `'invalid-locator' |
20
+ * 'manifest-error' | 'cache-error' | 'import-failed' | 'meta-missing' |
21
+ * 'meta-mismatch' | 'duplicate-id'`) so handlers can route on the failure mode.
22
+ */
23
+ export class RemotePluginLoadError extends BaseError.extend('RemotePluginLoadError', 'Failed to load remote plugin') {}
24
+
13
25
  /**
14
26
  * Abstraction over key-value storage (defaults to localStorage).
15
27
  */
@@ -24,9 +36,35 @@ export type Storage = {
24
36
  export type Options = {
25
37
  storage?: Storage;
26
38
  key?: string;
39
+ /**
40
+ * Per-platform offline asset cache. Defaults to a no-op cache (no offline support).
41
+ */
42
+ cache?: PluginAssetCache.Cache;
43
+ };
44
+
45
+ /**
46
+ * Options for the preload entry point. Adds an optional progress hook so hosts
47
+ * can drive a boot-loader counter (`Loading plugins (3/12)…`) as remote plugin
48
+ * manifests resolve. Resolution order is *not* deterministic — each entry races
49
+ * its own network fetch — so callers should treat `loaded` as a count, not an
50
+ * index. Failures are still swallowed; `loaded` advances on both success and
51
+ * recoverable failure so the counter always reaches `total`.
52
+ */
53
+ export type PreloadOptions = Options & {
54
+ onPluginLoaded?: (loaded: number, total: number) => void;
27
55
  };
28
56
 
29
- type RemotePluginEntry = { id: string; url: string };
57
+ /**
58
+ * Persisted record of a remote plugin that has been loaded previously.
59
+ *
60
+ * `url` is the URL of the plugin manifest (`plugin.json`), not the entry module.
61
+ */
62
+ export type RemotePluginEntry = {
63
+ id: string;
64
+ url: string;
65
+ /** Installed version string, e.g. `v1.0.0`. Populated after install from a community catalog entry. */
66
+ version?: string;
67
+ };
30
68
 
31
69
  const defaultStorage = (): Storage => ({
32
70
  get: (key) => localStorage.getItem(key),
@@ -58,6 +96,15 @@ const persistRemotePlugin = (storage: Storage, key: string, entry: RemotePluginE
58
96
  }
59
97
  };
60
98
 
99
+ const removePersistedRemotePlugin = (storage: Storage, key: string, pluginId: string): void => {
100
+ try {
101
+ const entries = getPersistedRemotePlugins(storage, key).filter((existing) => existing.id !== pluginId);
102
+ storage.set(key, JSON.stringify(entries));
103
+ } catch (error) {
104
+ log.warn('failed to remove remote plugin entry', { pluginId, error });
105
+ }
106
+ };
107
+
61
108
  const isUrl = (locator: string): boolean => {
62
109
  try {
63
110
  const url = new URL(locator);
@@ -67,6 +114,60 @@ const isUrl = (locator: string): boolean => {
67
114
  }
68
115
  };
69
116
 
117
+ /**
118
+ * Returns true when the URL's hostname is the local host (localhost, 127.0.0.1, or ::1).
119
+ */
120
+ export const isLocalUrl = (locator: string): boolean => {
121
+ try {
122
+ const hostname = new URL(locator).hostname.toLowerCase();
123
+ // WHATWG URL returns '::1' without brackets in Node; some browsers return '[::1]'.
124
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
125
+ } catch {
126
+ return false;
127
+ }
128
+ };
129
+
130
+ /**
131
+ * Returns the list of remote plugin entries previously persisted by {@link make}.
132
+ * Useful for UI code that needs to know which loaded plugins were installed from a URL
133
+ * (e.g. to surface a tag on remote or localhost-hosted plugins).
134
+ */
135
+ export const getRemoteEntries = (options: Options = {}): readonly RemotePluginEntry[] => {
136
+ const storage = options.storage ?? defaultStorage();
137
+ const key = options.key ?? DEFAULT_KEY;
138
+ return getPersistedRemotePlugins(storage, key);
139
+ };
140
+
141
+ /**
142
+ * Updates the persisted installed version for an already-stored remote plugin entry.
143
+ * Does nothing if the plugin has not been persisted yet.
144
+ */
145
+ export const setInstalledVersion = (id: string, version: string, options: Options = {}): void => {
146
+ const storage = options.storage ?? defaultStorage();
147
+ const key = options.key ?? DEFAULT_KEY;
148
+ const entries = getPersistedRemotePlugins(storage, key);
149
+ const index = entries.findIndex((entry) => entry.id === id);
150
+ if (index === -1) {
151
+ return;
152
+ }
153
+ entries[index] = { ...entries[index], version };
154
+ try {
155
+ storage.set(key, JSON.stringify(entries));
156
+ } catch (error) {
157
+ log.warn('failed to update installed version for remote plugin', { id, version, error });
158
+ }
159
+ };
160
+
161
+ /**
162
+ * Returns the installed version string for the given plugin id, or `undefined` if
163
+ * the plugin was installed without version tracking (legacy install) or is not found.
164
+ */
165
+ export const getInstalledVersion = (id: string, options: Options = {}): string | undefined => {
166
+ const storage = options.storage ?? defaultStorage();
167
+ const key = options.key ?? DEFAULT_KEY;
168
+ return getPersistedRemotePlugins(storage, key).find((entry) => entry.id === id)?.version;
169
+ };
170
+
70
171
  const normalizePluginExport = (mod: Record<string, unknown>): Plugin.Plugin => {
71
172
  const exported = mod.default;
72
173
  if (Plugin.isPlugin(exported)) {
@@ -81,68 +182,207 @@ const normalizePluginExport = (mod: Record<string, unknown>): Plugin.Plugin => {
81
182
  throw new Error('Remote module default export is not a Plugin or a zero-arg plugin factory.');
82
183
  };
83
184
 
84
- const loadRemotePlugin = async (url: string): Promise<Plugin.Plugin> => {
85
- log.info('loading remote plugin', { url });
86
- const mod = await import(/* @vite-ignore */ url);
87
- const plugin = normalizePluginExport(mod);
88
- if (!plugin.meta.id || !plugin.meta.name) {
89
- throw new Error(`Remote plugin at ${url} is missing required meta.id or meta.name.`);
90
- }
91
- return plugin;
92
- };
185
+ /**
186
+ * Loads stylesheets declared in the manifest by appending `<link rel="stylesheet">` elements to the host document.
187
+ * Each link is tagged with `data-dxos-plugin-id` so `uninstall` can clean them up.
188
+ */
189
+ const loadStylesheets = (
190
+ manifest: PluginManifest.ResolvedManifest,
191
+ cache: PluginAssetCache.Cache,
192
+ ): Effect.Effect<void, PluginAssetCache.PluginAssetCacheError> =>
193
+ Effect.gen(function* () {
194
+ if (typeof document === 'undefined') {
195
+ return;
196
+ }
197
+ const cssUrls = manifest.assetUrls.filter((url) => url.endsWith('.css'));
198
+ for (const url of cssUrls) {
199
+ const resolved = yield* cache.resolve(manifest.id, url);
200
+ if (document.querySelector(`link[data-dxos-plugin-id="${manifest.id}"][href="${resolved}"]`)) {
201
+ continue;
202
+ }
203
+ const link = document.createElement('link');
204
+ link.rel = 'stylesheet';
205
+ link.href = resolved;
206
+ link.dataset.dxosPluginId = manifest.id;
207
+ document.head.appendChild(link);
208
+ }
209
+ });
210
+
211
+ const loadFromManifest = (
212
+ manifestUrl: string,
213
+ cache: PluginAssetCache.Cache,
214
+ ): Effect.Effect<{ plugin: Plugin.Plugin; manifest: PluginManifest.ResolvedManifest }, RemotePluginLoadError> =>
215
+ Effect.gen(function* () {
216
+ log.info('loading remote plugin', { manifestUrl });
217
+ const manifest = yield* PluginManifest.fetchManifest(manifestUrl).pipe(
218
+ Effect.mapError(
219
+ (cause) => new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'manifest-error' }, cause }),
220
+ ),
221
+ );
222
+ const wrapCacheError = Effect.mapError(
223
+ (cause: PluginAssetCache.PluginAssetCacheError) =>
224
+ new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'cache-error' }, cause }),
225
+ );
226
+ // Dev-mode manifests (served by `vite dev` via composerPlugin) skip the offline
227
+ // cache: chunks resolve on demand through the dev server, the asset list isn't
228
+ // enumerable up-front, and CSS arrives via runtime <style> injection rather than
229
+ // static <link> tags.
230
+ if (!manifest.dev) {
231
+ // Cache the manifest URL alongside its declared assets. Without it, `preload` on a
232
+ // subsequent reload would fetch the manifest from the network — and fail when the
233
+ // plugin's host is offline, dropping the plugin from the runtime.
234
+ const cachedUrls =
235
+ manifest.assetUrls.indexOf(manifestUrl) === -1 ? [manifestUrl, ...manifest.assetUrls] : manifest.assetUrls;
236
+ yield* cache.cache(manifest.id, cachedUrls).pipe(wrapCacheError);
237
+ }
238
+ const entryUrl = manifest.dev
239
+ ? manifest.entryUrl
240
+ : yield* cache.resolve(manifest.id, manifest.entryUrl).pipe(wrapCacheError);
241
+ const mod = yield* Effect.tryPromise({
242
+ try: () => import(/* @vite-ignore */ entryUrl),
243
+ catch: (cause) =>
244
+ new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'import-failed' }, cause }),
245
+ });
246
+ const plugin = normalizePluginExport(mod);
247
+ if (!plugin.meta.id || !plugin.meta.name) {
248
+ return yield* Effect.fail(
249
+ new RemotePluginLoadError({ context: { locator: manifestUrl, reason: 'meta-missing' } }),
250
+ );
251
+ }
252
+ if (plugin.meta.id !== manifest.id) {
253
+ return yield* Effect.fail(
254
+ new RemotePluginLoadError({
255
+ context: {
256
+ locator: manifestUrl,
257
+ reason: 'meta-mismatch',
258
+ metaId: plugin.meta.id,
259
+ manifestId: manifest.id,
260
+ },
261
+ }),
262
+ );
263
+ }
264
+ // Append stylesheets only after the entry imports cleanly and meta validation
265
+ // passes. If we did this earlier and the import or meta checks failed, the
266
+ // `<link>` tags would leak into the host DOM with no plugin to own their
267
+ // teardown — `uninstall` only runs for plugins the manager actually accepted.
268
+ // In dev mode, Vite injects styles at runtime via the module graph, so static
269
+ // <link> tags would duplicate (or 404, since CSS chunks aren't pre-built).
270
+ if (!manifest.dev) {
271
+ yield* loadStylesheets(manifest, cache).pipe(wrapCacheError);
272
+ }
273
+ return { plugin, manifest };
274
+ });
93
275
 
94
276
  /**
95
- * Preloads previously persisted remote plugins from storage.
277
+ * Preloads previously persisted remote plugins from storage. Per-entry failures
278
+ * are logged and swallowed — the returned effect always succeeds with whichever
279
+ * plugins loaded cleanly, so a single bad entry can't block the host's startup.
96
280
  */
97
- export const preload = async (options: Options = {}): Promise<Plugin.Plugin[]> => {
98
- const storage = options.storage ?? defaultStorage();
99
- const key = options.key ?? DEFAULT_KEY;
281
+ export const preload = (options: PreloadOptions = {}): Effect.Effect<Plugin.Plugin[], never> =>
282
+ Effect.gen(function* () {
283
+ const storage = options.storage ?? defaultStorage();
284
+ const key = options.key ?? DEFAULT_KEY;
285
+ const cache = options.cache ?? PluginAssetCache.noop();
286
+ const onPluginLoaded = options.onPluginLoaded;
100
287
 
101
- const entries = getPersistedRemotePlugins(storage, key);
102
- if (entries.length === 0) {
103
- return [];
104
- }
105
- log.info('preloading remote plugins', { count: entries.length });
106
- const results = await Promise.allSettled(entries.map((entry) => loadRemotePlugin(entry.url)));
107
- const plugins: Plugin.Plugin[] = [];
108
- for (let index = 0; index < results.length; index++) {
109
- const result = results[index];
110
- if (result.status === 'fulfilled') {
111
- plugins.push(result.value);
112
- } else {
113
- log.warn('failed to preload remote plugin', { entry: entries[index], error: result.reason });
288
+ const entries = getPersistedRemotePlugins(storage, key);
289
+ if (entries.length === 0) {
290
+ return [];
114
291
  }
115
- }
116
- return plugins;
117
- };
292
+ log.info('preloading remote plugins', { count: entries.length });
293
+ const total = entries.length;
294
+ let loaded = 0;
295
+ const results = yield* Effect.all(
296
+ entries.map((entry) =>
297
+ loadFromManifest(entry.url, cache).pipe(
298
+ Effect.tapError((error) => Effect.sync(() => log.warn('failed to preload remote plugin', { entry, error }))),
299
+ Effect.option,
300
+ // Tick the progress hook on both success and recoverable failure so
301
+ // the counter monotonically reaches `total`. The host-supplied
302
+ // callback is best-effort: any synchronous throw flows through
303
+ // `Effect.try` and gets logged + ignored so a buggy hook can't
304
+ // derail the preload.
305
+ Effect.tap(() =>
306
+ Effect.sync(() => {
307
+ loaded += 1;
308
+ }).pipe(
309
+ Effect.andThen(Effect.try(() => onPluginLoaded?.(loaded, total))),
310
+ Effect.tapError((error) => Effect.sync(() => log.warn('onPluginLoaded threw', { loaded, total, error }))),
311
+ Effect.ignore,
312
+ ),
313
+ ),
314
+ ),
315
+ ),
316
+ { concurrency: 'unbounded' },
317
+ );
318
+ return results.flatMap((result) =>
319
+ Option.match(result, {
320
+ onNone: () => [],
321
+ onSome: ({ plugin }) => [plugin],
322
+ }),
323
+ );
324
+ });
118
325
 
119
326
  /**
120
327
  * Creates a plugin loader that resolves built-in plugins by ID or loads remote plugins from URLs.
328
+ *
329
+ * Remote URLs must point at a plugin manifest (`plugin.json`). The loader fetches the manifest,
330
+ * eagerly persists every declared asset via the configured `PluginAssetCache`, then dynamic-imports
331
+ * the entry module.
121
332
  */
122
333
  export const make = (builtinPlugins: Plugin.Plugin[], options: Options = {}) => {
123
334
  const storage = options.storage ?? defaultStorage();
124
335
  const key = options.key ?? DEFAULT_KEY;
336
+ const cache = options.cache ?? PluginAssetCache.noop();
125
337
 
126
- return (locator: string): Effect.Effect<Plugin.Plugin, Error> =>
338
+ return (locator: string): Effect.Effect<{ plugin: Plugin.Plugin; dev?: boolean }, RemotePluginLoadError> =>
127
339
  Effect.gen(function* () {
128
340
  const builtin = builtinPlugins.find((plugin) => plugin.meta.id === locator);
129
341
  if (builtin) {
130
- return builtin;
342
+ return { plugin: builtin };
131
343
  }
132
344
  if (!isUrl(locator)) {
133
- return yield* Effect.fail(new Error(`Plugin not found and locator is not a URL: ${locator}`));
345
+ return yield* Effect.fail(new RemotePluginLoadError({ context: { locator, reason: 'invalid-locator' } }));
134
346
  }
135
- const plugin = yield* Effect.tryPromise({
136
- try: () => loadRemotePlugin(locator),
137
- catch: (error) => new Error(`Failed to load remote plugin from ${locator}: ${error}`),
138
- });
139
- const duplicate = builtinPlugins.find((existing) => existing.meta.id === plugin.meta.id);
140
- if (duplicate) {
141
- return yield* Effect.fail(
142
- new Error(`Remote plugin ${plugin.meta.id} conflicts with built-in plugin of the same id.`),
143
- );
347
+ const { plugin, manifest } = yield* loadFromManifest(locator, cache);
348
+ // Production manifests can't collide with a builtin id — that would make
349
+ // the registered plugin permanently inaccessible. Dev manifests are
350
+ // allowed to collide because the manager shadow primitive handles the
351
+ // overlap (dev plugin takes the id slot for the session, original is
352
+ // restored on uninstall or page reload).
353
+ if (!manifest.dev) {
354
+ const duplicate = builtinPlugins.find((existing) => existing.meta.id === plugin.meta.id);
355
+ if (duplicate) {
356
+ return yield* Effect.fail(
357
+ new RemotePluginLoadError({ context: { locator, reason: 'duplicate-id', id: plugin.meta.id } }),
358
+ );
359
+ }
360
+ persistRemotePlugin(storage, key, { id: plugin.meta.id, url: locator });
144
361
  }
145
- persistRemotePlugin(storage, key, { id: plugin.meta.id, url: locator });
146
- return plugin;
362
+ return { plugin, dev: manifest.dev };
147
363
  });
148
364
  };
365
+
366
+ /**
367
+ * Removes a previously installed remote plugin: drops the persisted entry, evicts cached
368
+ * assets, and removes any stylesheet `<link>` tags that {@link loadFromManifest} appended.
369
+ *
370
+ * Cache eviction failures are logged and swallowed — the persisted entry has already been
371
+ * dropped so the user-visible state is consistent regardless of whether the platform
372
+ * cache cooperated.
373
+ */
374
+ export const uninstall = (pluginId: string, options: Options = {}): Effect.Effect<void, never> =>
375
+ Effect.gen(function* () {
376
+ const storage = options.storage ?? defaultStorage();
377
+ const key = options.key ?? DEFAULT_KEY;
378
+ const cache = options.cache ?? PluginAssetCache.noop();
379
+
380
+ removePersistedRemotePlugin(storage, key, pluginId);
381
+ if (typeof document !== 'undefined') {
382
+ document.querySelectorAll(`link[data-dxos-plugin-id="${pluginId}"]`).forEach((node) => node.remove());
383
+ }
384
+ yield* cache.evict(pluginId).pipe(
385
+ Effect.tapError((error) => Effect.sync(() => log.warn('failed to evict plugin assets', { pluginId, error }))),
386
+ Effect.ignore,
387
+ );
388
+ });
@@ -12,8 +12,8 @@ const HistoryCapabilities = Capability.lazy('HistoryCapabilities', () => import(
12
12
  export const OperationPlugin = Plugin.define(meta).pipe(
13
13
  Plugin.addModule({
14
14
  activatesOn: ActivationEvents.ManagedRuntimeReady,
15
- activatesBefore: [ActivationEvents.SetupOperationHandler],
16
- activatesAfter: [ActivationEvents.OperationInvokerReady],
15
+ firesBeforeActivation: [ActivationEvents.SetupOperationHandler],
16
+ firesAfterActivation: [ActivationEvents.OperationInvokerReady],
17
17
  activate: OperationInvoker,
18
18
  }),
19
19
  Plugin.addModule({
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
- import { type OperationInvoker } from '@dxos/operation';
7
+ import { OperationInvoker } from '@dxos/operation';
8
8
 
9
9
  import { Capabilities } from '../../common';
10
10
  import { Capability } from '../../core';
@@ -10,7 +10,8 @@ import * as ManagedRuntime from 'effect/ManagedRuntime';
10
10
  import * as TestClock from 'effect/TestClock';
11
11
  import { describe, expect } from 'vitest';
12
12
 
13
- import { Operation, OperationInvoker } from '@dxos/operation';
13
+ import { Operation } from '@dxos/compute';
14
+ import { OperationInvoker } from '@dxos/operation';
14
15
 
15
16
  const testRuntime = ManagedRuntime.make(Layer.empty) as unknown as ManagedRuntime.ManagedRuntime<any, any>;
16
17
 
@@ -7,7 +7,7 @@ import * as Stream from 'effect/Stream';
7
7
 
8
8
  import { runAndForwardErrors } from '@dxos/effect';
9
9
  import { log } from '@dxos/log';
10
- import { type OperationInvoker } from '@dxos/operation';
10
+ import { OperationInvoker } from '@dxos/operation';
11
11
 
12
12
  import { UndoOperation } from '../../common';
13
13
  import { EmptyHistoryError } from './errors';
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type { Operation } from '@dxos/operation';
5
+ import type { Operation } from '@dxos/compute';
6
6
 
7
7
  /**
8
8
  * History entry stored by HistoryTracker.
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type { Operation } from '@dxos/operation';
5
+ import type { Operation } from '@dxos/compute';
6
6
 
7
7
  /**
8
8
  * Label type for translatable text (canonical definition in @dxos/app-toolkit).
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type { Operation } from '@dxos/operation';
5
+ import type { Operation } from '@dxos/compute';
6
6
 
7
7
  import type * as UndoMapping from './undo-mapping';
8
8