@dxos/app-framework 0.9.0 → 0.9.1-main.c7dcc2e112

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 (208) hide show
  1. package/dist/lib/browser/{capability-BOPAKKWG.mjs → capability-Z4MRP3CP.mjs} +9 -9
  2. package/dist/lib/browser/{chunk-CO3XLNUX.mjs → chunk-37PG7XJC.mjs} +18 -18
  3. package/dist/lib/browser/chunk-37PG7XJC.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-66IXTIVK.mjs → chunk-3NQLTQCP.mjs} +6 -7
  5. package/dist/lib/browser/chunk-3NQLTQCP.mjs.map +7 -0
  6. package/dist/lib/browser/{chunk-KZUDO43J.mjs → chunk-3UTDRCKX.mjs} +31 -23
  7. package/dist/lib/browser/chunk-3UTDRCKX.mjs.map +7 -0
  8. package/dist/lib/browser/chunk-53HJHMS5.mjs +7 -0
  9. package/dist/lib/browser/chunk-53HJHMS5.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-IW44C7UL.mjs → chunk-CMINFVA5.mjs} +1 -1
  11. package/dist/lib/browser/chunk-CMINFVA5.mjs.map +7 -0
  12. package/dist/lib/browser/{chunk-Y66ELD4U.mjs → chunk-DFVSOBV2.mjs} +17 -7
  13. package/dist/lib/browser/chunk-DFVSOBV2.mjs.map +7 -0
  14. package/dist/lib/browser/{chunk-DC3WRPBV.mjs → chunk-S2ZBYQPI.mjs} +45 -12
  15. package/dist/lib/browser/chunk-S2ZBYQPI.mjs.map +7 -0
  16. package/dist/lib/browser/{chunk-HFLKXMT7.mjs → chunk-SWECIICH.mjs} +57 -41
  17. package/dist/lib/browser/chunk-SWECIICH.mjs.map +7 -0
  18. package/dist/lib/browser/{chunk-5GY3YOEL.mjs → chunk-VF37YTXJ.mjs} +5 -5
  19. package/dist/lib/browser/chunk-VF37YTXJ.mjs.map +7 -0
  20. package/dist/lib/browser/{chunk-XHS5KDNX.mjs → chunk-W4BHZING.mjs} +25 -25
  21. package/dist/lib/browser/chunk-W4BHZING.mjs.map +7 -0
  22. package/dist/lib/browser/{chunk-FO3IYSLV.mjs → chunk-YUVQIOTB.mjs} +29 -37
  23. package/dist/lib/browser/chunk-YUVQIOTB.mjs.map +7 -0
  24. package/dist/lib/browser/cli/index.mjs +9 -9
  25. package/dist/lib/browser/cli/index.mjs.map +2 -2
  26. package/dist/lib/browser/common/activation-events.mjs +7 -7
  27. package/dist/lib/browser/common/capabilities.mjs +7 -7
  28. package/dist/lib/browser/config/index.mjs +8 -0
  29. package/dist/lib/browser/config/index.mjs.map +7 -0
  30. package/dist/lib/browser/core/activation-event.mjs +1 -1
  31. package/dist/lib/browser/core/capability.mjs +1 -1
  32. package/dist/lib/browser/core/plugin-manager.mjs +4 -4
  33. package/dist/lib/browser/core/plugin.mjs +12 -2
  34. package/dist/lib/browser/core/url-loader.mjs +3 -3
  35. package/dist/lib/browser/index.mjs +14 -10
  36. package/dist/lib/browser/index.mjs.map +1 -1
  37. package/dist/lib/browser/meta.json +1 -1
  38. package/dist/lib/browser/{process-manager-capability-GXNUO7AR.mjs → process-manager-capability-RDROCCQD.mjs} +9 -9
  39. package/dist/lib/browser/testing/index.mjs +15 -15
  40. package/dist/lib/browser/testing/index.mjs.map +3 -3
  41. package/dist/lib/browser/testing/react.mjs +11 -11
  42. package/dist/lib/browser/testing/react.mjs.map +3 -3
  43. package/dist/lib/browser/ui/index.mjs +16 -10
  44. package/dist/lib/node-esm/{capability-KZWRTX7R.mjs → capability-34Z6NPVR.mjs} +9 -9
  45. package/dist/lib/node-esm/chunk-2UPUZXGU.mjs +9 -0
  46. package/dist/lib/node-esm/chunk-2UPUZXGU.mjs.map +7 -0
  47. package/dist/lib/node-esm/{chunk-FSVGKTF6.mjs → chunk-A4O42ZO3.mjs} +17 -7
  48. package/dist/lib/node-esm/chunk-A4O42ZO3.mjs.map +7 -0
  49. package/dist/lib/node-esm/{chunk-CTKEZHKF.mjs → chunk-AIDZMJ3U.mjs} +1 -1
  50. package/dist/lib/node-esm/chunk-AIDZMJ3U.mjs.map +7 -0
  51. package/dist/lib/node-esm/{chunk-XOCUANHO.mjs → chunk-BMW6P6QA.mjs} +6 -7
  52. package/dist/lib/node-esm/chunk-BMW6P6QA.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-A5CHGAZE.mjs → chunk-H7T7XXLS.mjs} +57 -41
  54. package/dist/lib/node-esm/chunk-H7T7XXLS.mjs.map +7 -0
  55. package/dist/lib/node-esm/{chunk-NULBHQYS.mjs → chunk-LUK3FCTH.mjs} +18 -18
  56. package/dist/lib/node-esm/chunk-LUK3FCTH.mjs.map +7 -0
  57. package/dist/lib/node-esm/{chunk-XGBUKRCK.mjs → chunk-TEQSMP2O.mjs} +31 -23
  58. package/dist/lib/node-esm/chunk-TEQSMP2O.mjs.map +7 -0
  59. package/dist/lib/node-esm/{chunk-KFZEB6BV.mjs → chunk-TLKYZKPW.mjs} +5 -5
  60. package/dist/lib/node-esm/chunk-TLKYZKPW.mjs.map +7 -0
  61. package/dist/lib/node-esm/{chunk-WK7OIQKI.mjs → chunk-VCPGX7WN.mjs} +29 -37
  62. package/dist/lib/node-esm/chunk-VCPGX7WN.mjs.map +7 -0
  63. package/dist/lib/node-esm/{chunk-XIGPWXCQ.mjs → chunk-WERVO5EK.mjs} +45 -12
  64. package/dist/lib/node-esm/chunk-WERVO5EK.mjs.map +7 -0
  65. package/dist/lib/node-esm/{chunk-SX5NVETP.mjs → chunk-ZRTG4TSS.mjs} +25 -25
  66. package/dist/lib/node-esm/chunk-ZRTG4TSS.mjs.map +7 -0
  67. package/dist/lib/node-esm/cli/index.mjs +9 -9
  68. package/dist/lib/node-esm/cli/index.mjs.map +2 -2
  69. package/dist/lib/node-esm/common/activation-events.mjs +7 -7
  70. package/dist/lib/node-esm/common/capabilities.mjs +7 -7
  71. package/dist/lib/node-esm/config/index.mjs +9 -0
  72. package/dist/lib/node-esm/config/index.mjs.map +7 -0
  73. package/dist/lib/node-esm/core/activation-event.mjs +1 -1
  74. package/dist/lib/node-esm/core/capability.mjs +1 -1
  75. package/dist/lib/node-esm/core/plugin-manager.mjs +4 -4
  76. package/dist/lib/node-esm/core/plugin.mjs +12 -2
  77. package/dist/lib/node-esm/core/url-loader.mjs +3 -3
  78. package/dist/lib/node-esm/index.mjs +14 -10
  79. package/dist/lib/node-esm/index.mjs.map +1 -1
  80. package/dist/lib/node-esm/meta.json +1 -1
  81. package/dist/lib/node-esm/{process-manager-capability-TX5TA6YN.mjs → process-manager-capability-6OCXSKUW.mjs} +9 -9
  82. package/dist/lib/node-esm/testing/index.mjs +15 -15
  83. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  84. package/dist/lib/node-esm/testing/react.mjs +11 -11
  85. package/dist/lib/node-esm/testing/react.mjs.map +3 -3
  86. package/dist/lib/node-esm/ui/index.mjs +16 -10
  87. package/dist/plugin/node-esm/index.mjs +406 -71
  88. package/dist/plugin/node-esm/index.mjs.map +4 -4
  89. package/dist/plugin/node-esm/meta.json +1 -1
  90. package/dist/types/src/cli/cli.d.ts +1 -1
  91. package/dist/types/src/common/activation-events.d.ts.map +1 -1
  92. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  93. package/dist/types/src/config/index.d.ts +2 -0
  94. package/dist/types/src/config/index.d.ts.map +1 -0
  95. package/dist/types/src/core/activation-event.d.ts +9 -6
  96. package/dist/types/src/core/activation-event.d.ts.map +1 -1
  97. package/dist/types/src/core/capability.d.ts +6 -2
  98. package/dist/types/src/core/capability.d.ts.map +1 -1
  99. package/dist/types/src/core/edge-registry-plugin-provider.d.ts +6 -11
  100. package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -1
  101. package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
  102. package/dist/types/src/core/plugin-manifest.d.ts +25 -7
  103. package/dist/types/src/core/plugin-manifest.d.ts.map +1 -1
  104. package/dist/types/src/core/plugin.d.ts +81 -91
  105. package/dist/types/src/core/plugin.d.ts.map +1 -1
  106. package/dist/types/src/core/registry.d.ts +17 -57
  107. package/dist/types/src/core/registry.d.ts.map +1 -1
  108. package/dist/types/src/core/url-loader.d.ts +2 -2
  109. package/dist/types/src/core/url-loader.d.ts.map +1 -1
  110. package/dist/types/src/index.d.ts +1 -0
  111. package/dist/types/src/index.d.ts.map +1 -1
  112. package/dist/types/src/plugin-process-manager/meta.d.ts +28 -2
  113. package/dist/types/src/plugin-process-manager/meta.d.ts.map +1 -1
  114. package/dist/types/src/testing/harness.d.ts +1 -1
  115. package/dist/types/src/testing/harness.d.ts.map +1 -1
  116. package/dist/types/src/testing/react.d.ts +6 -5
  117. package/dist/types/src/testing/react.d.ts.map +1 -1
  118. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  119. package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
  120. package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts +6 -0
  121. package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts.map +1 -1
  122. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +6 -10
  123. package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
  124. package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
  125. package/dist/types/src/ui/components/Surface/context.d.ts +6 -2
  126. package/dist/types/src/ui/components/Surface/context.d.ts.map +1 -1
  127. package/dist/types/src/ui/components/Surface/index.d.ts +10 -3
  128. package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
  129. package/dist/types/src/ui/components/Surface/types.d.ts +50 -70
  130. package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
  131. package/dist/types/src/ui/hooks/useApp.d.ts +1 -1
  132. package/dist/types/src/ui/hooks/useCapabilities.d.ts +11 -1
  133. package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
  134. package/dist/types/src/vite-plugin/composer/index.d.ts +11 -4
  135. package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -1
  136. package/dist/types/src/vite-plugin/index.d.ts +1 -0
  137. package/dist/types/src/vite-plugin/index.d.ts.map +1 -1
  138. package/dist/types/src/vite-plugin/load.d.ts +16 -0
  139. package/dist/types/src/vite-plugin/load.d.ts.map +1 -0
  140. package/dist/types/src/vite-plugin/manifest.d.ts +16 -4
  141. package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -1
  142. package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
  143. package/dist/types/tsconfig.tsbuildinfo +1 -1
  144. package/docs/registry-spec.md +331 -0
  145. package/moon.yml +1 -0
  146. package/package.json +32 -27
  147. package/src/cli/cli.ts +2 -2
  148. package/src/common/activation-events.ts +3 -3
  149. package/src/common/capabilities.ts +16 -16
  150. package/src/config/index.ts +5 -0
  151. package/src/core/activation-event.ts +15 -8
  152. package/src/core/capability.ts +15 -4
  153. package/src/core/edge-registry-plugin-provider.ts +41 -42
  154. package/src/core/plugin-manager.test.ts +133 -137
  155. package/src/core/plugin-manager.ts +25 -19
  156. package/src/core/plugin-manifest.test.ts +5 -5
  157. package/src/core/plugin-manifest.ts +13 -10
  158. package/src/core/plugin.ts +58 -114
  159. package/src/core/registry.ts +20 -60
  160. package/src/core/url-loader.test.ts +2 -2
  161. package/src/core/url-loader.ts +23 -19
  162. package/src/index.ts +1 -0
  163. package/src/testing/harness.ts +5 -3
  164. package/src/testing/react.test.tsx +4 -2
  165. package/src/testing/react.tsx +9 -8
  166. package/src/testing/service.ts +2 -2
  167. package/src/testing/withPluginManager.stories.tsx +4 -2
  168. package/src/testing/withPluginManager.tsx +3 -1
  169. package/src/ui/components/PluginManager/PluginManagerProvider.ts +7 -0
  170. package/src/ui/components/Surface/SurfaceComponent.stories.tsx +11 -14
  171. package/src/ui/components/Surface/SurfaceComponent.tsx +84 -74
  172. package/src/ui/components/Surface/context.ts +6 -3
  173. package/src/ui/components/Surface/index.ts +12 -1
  174. package/src/ui/components/Surface/types.test.ts +39 -49
  175. package/src/ui/components/Surface/types.ts +95 -60
  176. package/src/ui/hooks/useApp.test.tsx +1 -1
  177. package/src/ui/hooks/useApp.tsx +2 -2
  178. package/src/ui/hooks/useCapabilities.ts +24 -2
  179. package/src/vite-plugin/composer/index.ts +134 -61
  180. package/src/vite-plugin/index.ts +1 -0
  181. package/src/vite-plugin/load.ts +57 -0
  182. package/src/vite-plugin/manifest.test.ts +3 -3
  183. package/src/vite-plugin/manifest.ts +21 -10
  184. package/src/vite-plugin/packages.ts +0 -1
  185. package/dist/lib/browser/chunk-5GY3YOEL.mjs.map +0 -7
  186. package/dist/lib/browser/chunk-66IXTIVK.mjs.map +0 -7
  187. package/dist/lib/browser/chunk-CO3XLNUX.mjs.map +0 -7
  188. package/dist/lib/browser/chunk-DC3WRPBV.mjs.map +0 -7
  189. package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +0 -7
  190. package/dist/lib/browser/chunk-HFLKXMT7.mjs.map +0 -7
  191. package/dist/lib/browser/chunk-IW44C7UL.mjs.map +0 -7
  192. package/dist/lib/browser/chunk-KZUDO43J.mjs.map +0 -7
  193. package/dist/lib/browser/chunk-XHS5KDNX.mjs.map +0 -7
  194. package/dist/lib/browser/chunk-Y66ELD4U.mjs.map +0 -7
  195. package/dist/lib/node-esm/chunk-A5CHGAZE.mjs.map +0 -7
  196. package/dist/lib/node-esm/chunk-CTKEZHKF.mjs.map +0 -7
  197. package/dist/lib/node-esm/chunk-FSVGKTF6.mjs.map +0 -7
  198. package/dist/lib/node-esm/chunk-KFZEB6BV.mjs.map +0 -7
  199. package/dist/lib/node-esm/chunk-NULBHQYS.mjs.map +0 -7
  200. package/dist/lib/node-esm/chunk-SX5NVETP.mjs.map +0 -7
  201. package/dist/lib/node-esm/chunk-WK7OIQKI.mjs.map +0 -7
  202. package/dist/lib/node-esm/chunk-XGBUKRCK.mjs.map +0 -7
  203. package/dist/lib/node-esm/chunk-XIGPWXCQ.mjs.map +0 -7
  204. package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +0 -7
  205. /package/dist/lib/browser/{capability-BOPAKKWG.mjs.map → capability-Z4MRP3CP.mjs.map} +0 -0
  206. /package/dist/lib/browser/{process-manager-capability-GXNUO7AR.mjs.map → process-manager-capability-RDROCCQD.mjs.map} +0 -0
  207. /package/dist/lib/node-esm/{capability-KZWRTX7R.mjs.map → capability-34Z6NPVR.mjs.map} +0 -0
  208. /package/dist/lib/node-esm/{process-manager-capability-TX5TA6YN.mjs.map → process-manager-capability-6OCXSKUW.mjs.map} +0 -0
@@ -2,10 +2,13 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
+ import { readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
5
7
  import { type Plugin as VitePlugin } from 'vite';
6
8
 
7
- import { PLUGIN_DEV_SERVER_PORT } from '../../core';
8
- import { type BuildMeta, ENTRY_FILENAME, MANIFEST_ASSET_NAME, serializeManifest } from '../manifest';
9
+ import { PLUGIN_DEV_SERVER_PORT, Plugin } from '../../core';
10
+ import { findDxConfigFile, loadDxConfig } from '../load';
11
+ import { type BuildMeta, ENTRY_FILENAME, MANIFEST_ASSET_NAME, serializeManifest, toBuildMeta } from '../manifest';
9
12
  import { DEFAULT_PACKAGES, isSharedPackage } from '../packages';
10
13
 
11
14
  export { ENTRY_FILENAME, MANIFEST_ASSET_NAME, serializeManifest };
@@ -13,6 +16,45 @@ export type { BuildMeta };
13
16
 
14
17
  const JSX_DEV_RUNTIME = 'react/jsx-dev-runtime';
15
18
 
19
+ /** Reads `version` from the project's `package.json`; falls back to `0.0.0`. */
20
+ const readPackageVersion = (dir: string): string => {
21
+ try {
22
+ return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8')).version ?? '0.0.0';
23
+ } catch {
24
+ return '0.0.0';
25
+ }
26
+ };
27
+
28
+ /**
29
+ * Snapshot the project's declared `dependencies`, resolving each to its concrete
30
+ * installed version (read from `node_modules/<name>/package.json`) so the manifest
31
+ * records exactly what the plugin was built against — the host derives SDK
32
+ * compatibility from the `@dxos/*` subset. Falls back to the declared spec when a
33
+ * dependency can't be resolved on disk.
34
+ */
35
+ const readResolvedDependencies = (dir: string): Record<string, string> | undefined => {
36
+ try {
37
+ const declared = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8')).dependencies as
38
+ | Record<string, string>
39
+ | undefined;
40
+ if (!declared) {
41
+ return undefined;
42
+ }
43
+ const resolved: Record<string, string> = {};
44
+ for (const name of Object.keys(declared)) {
45
+ try {
46
+ const version = JSON.parse(readFileSync(join(dir, 'node_modules', name, 'package.json'), 'utf-8')).version;
47
+ resolved[name] = typeof version === 'string' ? version : declared[name];
48
+ } catch {
49
+ resolved[name] = declared[name];
50
+ }
51
+ }
52
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ };
57
+
16
58
  /**
17
59
  * Whether an import should be externalized by the plugin bundle.
18
60
  * Uses the shared-package predicate directly so plugins automatically pick up any
@@ -71,10 +113,17 @@ export type ComposerPluginOptions = {
71
113
  /** Dev server port. Defaults to {@link PLUGIN_DEV_SERVER_PORT}. */
72
114
  port?: number;
73
115
  /**
74
- * Plugin metadata. When provided, a `manifest.json` asset is emitted alongside the bundle
75
- * listing every emitted file. The host fetches this manifest at install time and persists
76
- * the declared assets in its offline cache so the plugin works without network. The bundle's
77
- * entry module is always written as {@link ENTRY_FILENAME} (`index.mjs`).
116
+ * Path to the project's `dx.config.ts`. Defaults to auto-discovery in the project root
117
+ * (`dx.config.{ts,mjs,js}`). Used only to derive the manifest metadata.
118
+ */
119
+ config?: string;
120
+ /**
121
+ * Plugin metadata. Optional — when omitted it is derived from the project's `dx.config.ts`
122
+ * (`@dxos/protocols` `Config2.Config`) plus the `package.json` version and resolved dependencies. When
123
+ * present (explicitly or derived), a `manifest.json` asset is emitted alongside the bundle listing
124
+ * every emitted file. The host fetches this manifest at install time and persists the declared
125
+ * assets in its offline cache so the plugin works without network. The bundle's entry module is
126
+ * always written as {@link ENTRY_FILENAME} (`index.mjs`).
78
127
  */
79
128
  meta?: BuildMeta;
80
129
  };
@@ -97,7 +146,25 @@ export type ComposerPluginOptions = {
97
146
  export const composerPlugin = (options?: ComposerPluginOptions): VitePlugin[] => {
98
147
  const entry = options?.entry ?? 'src/plugin.tsx';
99
148
  const port = options?.port ?? PLUGIN_DEV_SERVER_PORT;
100
- const meta = options?.meta;
149
+ const projectRoot = process.cwd();
150
+
151
+ // Plugin metadata source of truth is `dx.config.ts` (`@dxos/protocols` `Config2.Config`). When the caller
152
+ // doesn't pass `meta` explicitly, load + validate the config and derive a `BuildMeta` from it
153
+ // (augmented with the package `version` and a resolved dependency snapshot). Resolved once,
154
+ // lazily, so the synchronous plugin factory stays sync; the manifest hooks await it.
155
+ const metaPromise: Promise<BuildMeta | undefined> = options?.meta
156
+ ? Promise.resolve(options.meta)
157
+ : Promise.resolve(options?.config ?? findDxConfigFile(projectRoot)).then((configFile) =>
158
+ configFile
159
+ ? loadDxConfig(configFile).then((config) =>
160
+ toBuildMeta(
161
+ Plugin.getMetaFromConfig(config),
162
+ readPackageVersion(projectRoot),
163
+ readResolvedDependencies(projectRoot),
164
+ ),
165
+ )
166
+ : undefined,
167
+ );
101
168
  const resolved = new Set<string>();
102
169
  let base = '/';
103
170
 
@@ -246,61 +313,67 @@ export const composerPlugin = (options?: ComposerPluginOptions): VitePlugin[] =>
246
313
  },
247
314
  ];
248
315
 
249
- if (meta) {
250
- // Emit `manifest.json` alongside the built module listing every emitted asset, so the
251
- // dist directory can be uploaded as-is to a GitHub Release for the DXOS community
252
- // registry to pick up. The host loader fetches this file at install time and eagerly
253
- // caches every listed asset for offline use.
254
- plugins.push({
255
- name: 'composer-plugin:emit-manifest',
256
- apply: 'build',
257
- // `enforce: 'post'` is load-bearing: vite's CSS extraction plugin emits sibling
258
- // `.css` assets in its own `generateBundle` hook. Without `post`, ours runs first
259
- // and sees only the JS chunks — the manifest then omits CSS, so the host can't
260
- // inject `<link>` tags for the plugin's stylesheet at install time.
261
- enforce: 'post',
262
- generateBundle(_options, bundle) {
263
- // Source maps are large and only fetched when devtools opens — exclude them
264
- // from the manifest so the host doesn't precache megabytes of debug data.
265
- const assets = Object.keys(bundle)
266
- .filter((name) => name !== MANIFEST_ASSET_NAME && !name.endsWith('.map'))
267
- .sort();
268
- this.emitFile({
269
- type: 'asset',
270
- fileName: MANIFEST_ASSET_NAME,
271
- source: serializeManifest(meta, { assets }),
272
- });
273
- },
274
- });
316
+ // Emit `manifest.json` alongside the built module listing every emitted asset, so the
317
+ // dist directory can be uploaded as-is to a GitHub Release for the DXOS community
318
+ // registry to pick up. The host loader fetches this file at install time and eagerly
319
+ // caches every listed asset for offline use. No-ops when no `dx.config.ts`/`meta` resolves.
320
+ plugins.push({
321
+ name: 'composer-plugin:emit-manifest',
322
+ apply: 'build',
323
+ // `enforce: 'post'` is load-bearing: vite's CSS extraction plugin emits sibling
324
+ // `.css` assets in its own `generateBundle` hook. Without `post`, ours runs first
325
+ // and sees only the JS chunks the manifest then omits CSS, so the host can't
326
+ // inject `<link>` tags for the plugin's stylesheet at install time.
327
+ enforce: 'post',
328
+ async generateBundle(_options, bundle) {
329
+ const meta = await metaPromise;
330
+ if (!meta) {
331
+ return;
332
+ }
333
+ // Source maps are large and only fetched when devtools opens — exclude them
334
+ // from the manifest so the host doesn't precache megabytes of debug data.
335
+ const assets = Object.keys(bundle)
336
+ .filter((name) => name !== MANIFEST_ASSET_NAME && !name.endsWith('.map'))
337
+ .sort();
338
+ this.emitFile({
339
+ type: 'asset',
340
+ fileName: MANIFEST_ASSET_NAME,
341
+ source: serializeManifest(meta, { assets }),
342
+ });
343
+ },
344
+ });
275
345
 
276
- // Dev-server manifest. `vite dev` can't enumerate transitive chunks/CSS up front
277
- // (they resolve on demand), so we serve a `devEntry`-flavored manifest at the dev
278
- // root and let the host loader skip eager caching + static stylesheet injection.
279
- // The host imports `entry` directly from the dev server, where Vite handles
280
- // JSX transforms, externalization, and runtime CSS injection.
281
- plugins.push({
282
- name: 'composer-plugin:serve-manifest',
283
- apply: 'serve',
284
- configureServer(server) {
285
- const body = serializeManifest(meta, { assets: [], devEntry: entry.replace(/^\.?\//, '') });
286
- server.middlewares.use(`/${MANIFEST_ASSET_NAME}`, (req, res, next) => {
287
- // Only intercept the bare manifest URL — let any sub-paths fall through.
288
- if (req.url && req.url !== '/' && req.url !== '') {
289
- return next();
290
- }
291
- // CORS mirrors `server.cors: true` (see plugin config above) so the host
292
- // app at a different origin can read the manifest. Vite's built-in CORS
293
- // covers most paths but middlewares mounted before its `cors` middleware
294
- // need to set the header themselves.
295
- res.setHeader('Access-Control-Allow-Origin', '*');
296
- res.setHeader('Content-Type', 'application/json');
297
- res.setHeader('Cache-Control', 'no-store');
298
- res.statusCode = 200;
299
- res.end(body);
300
- });
301
- },
302
- });
303
- }
346
+ // Dev-server manifest. `vite dev` can't enumerate transitive chunks/CSS up front
347
+ // (they resolve on demand), so we serve a `devEntry`-flavored manifest at the dev
348
+ // root and let the host loader skip eager caching + static stylesheet injection.
349
+ // The host imports `entry` directly from the dev server, where Vite handles
350
+ // JSX transforms, externalization, and runtime CSS injection.
351
+ plugins.push({
352
+ name: 'composer-plugin:serve-manifest',
353
+ apply: 'serve',
354
+ async configureServer(server) {
355
+ const meta = await metaPromise;
356
+ if (!meta) {
357
+ return;
358
+ }
359
+ const body = serializeManifest(meta, { assets: [], devEntry: entry.replace(/^\.?\//, '') });
360
+ server.middlewares.use(`/${MANIFEST_ASSET_NAME}`, (req, res, next) => {
361
+ // Only intercept the bare manifest URL let any sub-paths fall through.
362
+ if (req.url && req.url !== '/' && req.url !== '') {
363
+ return next();
364
+ }
365
+ // CORS mirrors `server.cors: true` (see plugin config above) so the host
366
+ // app at a different origin can read the manifest. Vite's built-in CORS
367
+ // covers most paths but middlewares mounted before its `cors` middleware
368
+ // need to set the header themselves.
369
+ res.setHeader('Access-Control-Allow-Origin', '*');
370
+ res.setHeader('Content-Type', 'application/json');
371
+ res.setHeader('Cache-Control', 'no-store');
372
+ res.statusCode = 200;
373
+ res.end(body);
374
+ });
375
+ },
376
+ });
304
377
 
305
378
  return plugins;
306
379
  };
@@ -8,3 +8,4 @@ export * from './import-map';
8
8
  // `manifest` and `packages` are shared data used by composer / import-map;
9
9
  // they stay at the top level because they're not vite plugins themselves.
10
10
  export * from './packages';
11
+ export { findDxConfigFile, loadDxConfig } from './load';
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+ import { build } from 'esbuild';
7
+ import { randomUUID } from 'node:crypto';
8
+ import { existsSync } from 'node:fs';
9
+ import { rm, writeFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
12
+
13
+ import { Config2 } from '@dxos/protocols';
14
+
15
+ const CONFIG_BASENAMES = ['dx.config.ts', 'dx.config.mjs', 'dx.config.js'];
16
+
17
+ const decodeConfig2 = Schema.decodeUnknownSync(Config2.Config);
18
+
19
+ /**
20
+ * Resolves the first `dx.config.{ts,mjs,js}` found under `dir`, or `undefined` if none exist.
21
+ * Pass the result to {@link loadDxConfig}.
22
+ */
23
+ export const findDxConfigFile = (dir: string): string | undefined =>
24
+ CONFIG_BASENAMES.map((basename) => join(dir, basename)).find((candidate) => existsSync(candidate));
25
+
26
+ /**
27
+ * Loads and validates a `dx.config.ts` (or `.mjs`/`.js`) file at the given `filePath`.
28
+ *
29
+ * Transpiles the file with esbuild — leaving its bare imports (`@dxos/protocols`, `@dxos/app-framework`, …)
30
+ * external so they resolve from the project's `node_modules` — imports the result, then decodes the
31
+ * default export against the `Config2` schema (a malformed config throws a `Schema` parse error).
32
+ * Node-only (esbuild + filesystem); used by `composerPlugin` and `dx registry publish`.
33
+ */
34
+ export const loadDxConfig = async (filePath: string): Promise<Config2.Config> => {
35
+ const result = await build({
36
+ entryPoints: [filePath],
37
+ bundle: true,
38
+ format: 'esm',
39
+ platform: 'node',
40
+ write: false,
41
+ // Leave node_modules (incl. @dxos/*) as runtime imports so the temp module below resolves them
42
+ // from the project's node_modules rather than inlining heavy deps (effect, etc.) on every load.
43
+ packages: 'external',
44
+ logLevel: 'silent',
45
+ });
46
+
47
+ // Import via a temp file so its externalized bare specifiers resolve against the project's
48
+ // node_modules (a data-URL import cannot resolve bare specifiers).
49
+ const tempPath = join(filePath, '..', `.dx.config.${randomUUID()}.mjs`);
50
+ try {
51
+ await writeFile(tempPath, result.outputFiles[0].text, 'utf-8');
52
+ const module = await import(pathToFileURL(tempPath).href);
53
+ return decodeConfig2(module.default);
54
+ } finally {
55
+ await rm(tempPath, { force: true });
56
+ }
57
+ };
@@ -12,7 +12,7 @@ import { type BuildMeta, serializeManifest } from './manifest';
12
12
  // Build a `BuildMeta` (runtime `Meta` plus the build-time `version`, which may be a
13
13
  // non-semver dev tag and so lives outside the `key` DXN).
14
14
  const buildMeta = (options: Plugin.MakeMetaOptions, version: string): BuildMeta => ({
15
- ...Plugin.makeMeta(options),
15
+ ...Plugin.makeMeta(options).profile,
16
16
  version,
17
17
  });
18
18
 
@@ -23,7 +23,7 @@ describe('serializeManifest', () => {
23
23
  { assets: ['index.mjs', 'style.css', 'chunks/lib-abc.js'] },
24
24
  );
25
25
  expect(JSON.parse(json)).toEqual({
26
- id: 'org.example.plugin',
26
+ key: 'org.example.plugin',
27
27
  name: 'Example',
28
28
  description: 'Demo',
29
29
  tags: ['new'],
@@ -38,7 +38,7 @@ describe('serializeManifest', () => {
38
38
  devEntry: 'src/plugin.tsx',
39
39
  });
40
40
  expect(JSON.parse(json)).toEqual({
41
- id: 'org.example.plugin',
41
+ key: 'org.example.plugin',
42
42
  name: 'Example',
43
43
  version: '0.0.0-dev',
44
44
  assets: [],
@@ -20,12 +20,28 @@ export const MANIFEST_ASSET_NAME = 'manifest.json';
20
20
  export const ENTRY_FILENAME = PLUGIN_ENTRY_FILENAME;
21
21
 
22
22
  /**
23
- * Plugin metadata required to emit a manifest at build time.
23
+ * Flat plugin metadata emitted into `manifest.json` at build time (the shape validated by
24
+ * `@dxos/protocols` `PluginManifestSchema`).
24
25
  *
25
- * Extends `Plugin.Meta` with a build-time `version` field that is not
26
- * relevant to the runtime plugin definition itself.
26
+ * The runtime {@link Plugin.Meta} nests its display/identity fields under `profile`; the build
27
+ * manifest is intentionally flat, so this is the {@link Plugin.Profile} content plus build-time fields:
28
+ * the package `version`, and a `dependencies` snapshot (every declared dependency resolved to its
29
+ * concrete installed version). The host derives SDK compatibility from the subset of `dependencies`
30
+ * it shares with the plugin (the externalized `@dxos/*` packages); the rest are recorded for transparency.
27
31
  */
28
- export type BuildMeta = Plugin.Meta & { version: string };
32
+ export type BuildMeta = Plugin.Profile & { version: string; dependencies?: Record<string, string> };
33
+
34
+ /**
35
+ * Flattens a runtime {@link Plugin.Meta}'s `profile` and augments it with the build-time fields
36
+ * needed to emit `manifest.json`: the package `version` (from the publishing project's `package.json`)
37
+ * and an optional resolved `dependencies` snapshot. Produces the {@link BuildMeta} that `composerPlugin`
38
+ * serializes.
39
+ */
40
+ export const toBuildMeta = (meta: Plugin.Meta, version: string, dependencies?: Record<string, string>): BuildMeta => ({
41
+ ...meta.profile,
42
+ version,
43
+ ...(dependencies ? { dependencies } : {}),
44
+ });
29
45
 
30
46
  /**
31
47
  * Serializes a plugin's public metadata + bundle layout into the format consumed
@@ -46,14 +62,9 @@ export const serializeManifest = (
46
62
  meta: BuildMeta,
47
63
  { assets, devEntry }: { assets: readonly string[]; devEntry?: string },
48
64
  ): string => {
49
- // The published manifest is keyed by the bare NSID `id`; the `key` DXN is an
50
- // in-process convenience and is intentionally omitted to keep the registry
51
- // wire-format stable. `version` is the build-time package version (may be a
52
- // non-semver dev tag), carried by `BuildMeta` rather than derived from `key`.
53
- const { key: _key, ...rest } = meta;
54
65
  return JSON.stringify(
55
66
  {
56
- ...rest,
67
+ ...meta,
57
68
  assets,
58
69
  ...(devEntry !== undefined ? { devEntry } : {}),
59
70
  },
@@ -160,7 +160,6 @@ export const DEFAULT_PACKAGES = [
160
160
  '@dxos/react-ui-syntax-highlighter',
161
161
  '@dxos/react-ui-table',
162
162
  '@dxos/react-ui-tabs',
163
- '@dxos/react-ui-text-tooltip',
164
163
  '@dxos/react-ui-thread',
165
164
  '@dxos/solid-ui',
166
165
  '@dxos/solid-ui-geo',
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/common/activation-events.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { ActivationEvent as ActivationEvent$ } from '../core';\n\n/**\n * Fired when the app is started.\n */\nexport const Startup = ActivationEvent$.make('org.dxos.app-framework.event.startup');\n\n//\n// Dependent Events\n//\n\n/**\n * Fired to load any newly available surfaces.\n */\nexport const SetupReactSurface = ActivationEvent$.make('org.dxos.app-framework.event.setup-react-surface');\n\n/**\n * Fired before the process manager is created.\n * Plugins should contribute their {@link Capabilities.LayerSpec} entries and\n * {@link Capabilities.OperationHandler} sets before this event fires so the\n * process manager's {@link ServiceResolver} and {@link OperationInvoker} pick\n * them up at construction time.\n */\nexport const SetupProcessManager = ActivationEvent$.make('org.dxos.app-framework.event.setup-process-manager');\n\n//\n// Triggered Events\n//\n\n/**\n * Fired after the process manager runtime is ready and its derived capabilities\n * (`ProcessManagerRuntime`, `ServiceResolver`, `ProcessMonitor`, `OperationInvoker`)\n * have been contributed.\n */\nexport const ProcessManagerReady = ActivationEvent$.make('org.dxos.app-framework.event.process-manager-ready');\n"],
5
- "mappings": ";;;;;;;;AAAA;;;;;;;AASO,IAAMA,UAAUC,yBAAiBC,KAAK,sCAAA;AAStC,IAAMC,oBAAoBF,yBAAiBC,KAAK,kDAAA;AAShD,IAAME,sBAAsBH,yBAAiBC,KAAK,oDAAA;AAWlD,IAAMG,sBAAsBJ,yBAAiBC,KAAK,oDAAA;",
6
- "names": ["Startup", "ActivationEvent$", "make", "SetupReactSurface", "SetupProcessManager", "ProcessManagerReady"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/core/activation-event.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { compositeKey } from '@dxos/util';\n\n/**\n * A unique string identifier representing an event.\n * This is expected to be a URI, where initial parts are often the id of the plugin whose package defines the event.\n *\n * @example org.dxos.plugin.example.event.ready\n */\nexport type ActivationEvent = {\n id: string;\n specifier?: string;\n};\n\n/**\n * An activation event that can be a single event, or a combination of events.\n */\nexport type Events =\n | ActivationEvent\n | { type: 'one-of'; events: ActivationEvent[] }\n | { type: 'all-of'; events: ActivationEvent[] };\n\n/**\n * Helper to define an activation event.\n */\nexport const make = (id: string, specifier?: string) => {\n return { id, specifier } as ActivationEvent;\n};\n\n/**\n * Helper to create an activation event key.\n */\nexport const eventKey = (event: ActivationEvent) =>\n event.specifier ? compositeKey(event.id, event.specifier) : event.id;\n\n/**\n * Helper to create an activation event that triggers when any of the given events are activated.\n */\nexport const oneOf = (...events: ActivationEvent[]) => ({ type: 'one-of' as const, events });\n\n/**\n * Helper to create an activation event that triggers when all of the given events are activated.\n */\nexport const allOf = (...events: ActivationEvent[]) => ({ type: 'all-of' as const, events });\n\n/**\n * Helper to check if an activation event is a one-of event.\n */\nexport const isOneOf = (events: Events): events is { type: 'one-of'; events: ActivationEvent[] } =>\n 'type' in events && events.type === 'one-of';\n\n/**\n * Helper to check if an activation event is an all-of event.\n */\nexport const isAllOf = (events: Events): events is { type: 'all-of'; events: ActivationEvent[] } =>\n 'type' in events && events.type === 'all-of';\n\n/**\n * Helper to get the events from an activation event.\n */\nexport const getEvents = (events: Events) => ('type' in events ? events.events : [events]);\n"],
5
- "mappings": ";;;;;AAAA;;;;;;;;;;AAIA,SAASA,oBAAoB;AAwBtB,IAAMC,OAAO,CAACC,IAAYC,cAAAA;AAC/B,SAAO;IAAED;IAAIC;EAAU;AACzB;AAKO,IAAMC,WAAW,CAACC,UACvBA,MAAMF,YAAYH,aAAaK,MAAMH,IAAIG,MAAMF,SAAS,IAAIE,MAAMH;AAK7D,IAAMI,QAAQ,IAAIC,YAA+B;EAAEC,MAAM;EAAmBD;AAAO;AAKnF,IAAME,QAAQ,IAAIF,YAA+B;EAAEC,MAAM;EAAmBD;AAAO;AAKnF,IAAMG,UAAU,CAACH,WACtB,UAAUA,UAAUA,OAAOC,SAAS;AAK/B,IAAMG,UAAU,CAACJ,WACtB,UAAUA,UAAUA,OAAOC,SAAS;AAK/B,IAAMI,YAAY,CAACL,WAAoB,UAAUA,SAASA,OAAOA,SAAS;EAACA;;",
6
- "names": ["compositeKey", "make", "id", "specifier", "eventKey", "event", "oneOf", "events", "type", "allOf", "isOneOf", "isAllOf", "getEvents"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/common/capabilities.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Atom, type Registry } from '@effect-atom/atom-react';\nimport type * as Command$ from '@effect/cli/Command';\nimport * as Effect from 'effect/Effect';\nimport type * as Exit$ from 'effect/Exit';\nimport type * as Fiber$ from 'effect/Fiber';\nimport type * as Layer$ from 'effect/Layer';\nimport type * as ManagedRuntime$ from 'effect/ManagedRuntime';\nimport type * as Runtime$ from 'effect/Runtime';\nimport type { FC, PropsWithChildren } from 'react';\n\nimport type {\n LayerSpec as LayerSpec$,\n Operation as Operation$,\n OperationHandlerSet,\n Process as Process$,\n ServiceResolver as ServiceResolver$,\n Trace as Trace$,\n} from '@dxos/compute';\nimport type { ProcessManager as ProcessManager$ } from '@dxos/compute-runtime';\nimport { OperationInvoker as OperationInvoker$ } from '@dxos/operation';\n\nimport { Capability as Capability$, Plugin as Plugin$, type PluginManager as PluginManager$ } from '../core';\nimport type {\n HistoryTracker as HistoryTracker$,\n UndoMapping as UndoMapping$,\n UndoRegistry as UndoRegistry$,\n} from '../plugin-process-manager';\nimport type { Surface } from '../ui';\n\n/**\n * Null capability.\n * @category Capability\n */\nexport const Null = Capability$.make<null>('org.dxos.app-framework.capability.null');\n\n/**\n * @category Capability\n */\nexport const PluginManager = Capability$.make<PluginManager$.PluginManager>(\n 'org.dxos.app-framework.capability.plugin-manager',\n);\n\n/**\n * @category Capability\n */\nexport const AtomRegistry = Capability$.make<Registry.Registry>('org.dxos.app-framework.capability.atom-registry');\n\nexport type ReactContext = Readonly<{\n id: string;\n dependsOn?: string[];\n context: FC<PropsWithChildren>;\n}>;\n\n/**\n * @category Capability\n */\nexport const ReactContext = Capability$.make<ReactContext>('org.dxos.app-framework.capability.react-context');\n\nexport type ReactRoot = Readonly<{ id: string; root: FC<PropsWithChildren> }>;\n\n/**\n * @category Capability\n */\nexport const ReactRoot = Capability$.make<ReactRoot>('org.dxos.app-framework.capability.react-root');\n\n/**\n * Surface definitions that can be either React components or Web Components.\n */\nexport type ReactSurface = Surface.Definition | readonly Surface.Definition[];\n\n/**\n * @category Capability\n */\nexport const ReactSurface = Capability$.make<ReactSurface>('org.dxos.app-framework.common.react-surface');\n\nexport type AnyCommand = Command$.Command<any, any, any, any>;\n\n/**\n * @category Capability\n */\nexport const Command = Capability$.make<AnyCommand>('org.dxos.app-framework.capability.command');\n\n/**\n * @category Capability\n */\nexport const Layer = Capability$.make<Layer$.Layer<any, any, any>>('org.dxos.app-framework.capability.layer');\n\n/**\n * Layer specification contributed by plugins.\n *\n * Plugins contribute {@link LayerSpec.LayerSpec} entries that are collected by the\n * process-manager module and composed into a {@link LayerStack} which backs the\n * {@link ProcessManagerRuntime}'s service resolver.\n *\n * @category Capability\n */\nexport const LayerSpec = Capability$.make<LayerSpec$.LayerSpec>('org.dxos.app-framework.capability.layer-spec');\n\n/**\n * Context passed to {@link TraceSinkFactory} implementations when the\n * process-manager capability materialises contributed sinks.\n */\nexport interface TraceSinkFactoryContext {\n /**\n * Service resolver backing the shared {@link ProcessManagerRuntime}. Use it\n * to resolve per-space (or per-process) services like `FeedTraceSink` when\n * building a routing sink.\n */\n readonly resolver: ServiceResolver$.ServiceResolver;\n}\n\n/**\n * Factory that builds a {@link Trace$.Sink} when the process-manager\n * capability is ready. Plugins that only need a static sink can ignore the\n * context (e.g. `() => myConsoleSink`); plugins that need per-space routing\n * can use {@link TraceSinkFactoryContext.resolver} to look up services.\n */\nexport type TraceSinkFactory = (ctx: TraceSinkFactoryContext) => Trace$.Sink;\n\n/**\n * Trace sink contribution.\n *\n * Plugins contribute {@link TraceSinkFactory} functions; the process-manager\n * capability invokes each factory with the runtime's\n * {@link ServiceResolver$.ServiceResolver}, collects the resulting\n * {@link Trace$.Sink}s, merges them via {@link Trace$.mergeSinks}, and\n * installs the merged sink as {@link Trace$.TraceSink} in the runtime layer\n * so every process writes to every contributed sink.\n *\n * @category Capability\n */\nexport const TraceSink = Capability$.make<TraceSinkFactory>('org.dxos.app-framework.capability.trace-sink');\n\n/**\n * Service resolver backing the shared {@link ProcessManagerRuntime}.\n *\n * Contributed by the process-manager capability module. Consumers can combine\n * it with {@link ServiceResolver$.provide} to build space-scoped layers without\n * having to go through the process-manager runtime:\n *\n * @example\n * ```ts\n * const resolver = yield* Capability.get(Capabilities.ServiceResolver);\n * yield* effect.pipe(\n * Effect.provide(\n * ServiceResolver.provide({ space }, Database.Service, Feed.FeedService).pipe(\n * Layer.provide(Layer.succeed(ServiceResolver.ServiceResolver, resolver)),\n * ),\n * ),\n * );\n * ```\n *\n * @category Capability\n */\nexport const ServiceResolver = Capability$.make<ServiceResolver$.ServiceResolver>(\n 'org.dxos.app-framework.capability.service-resolver',\n);\n\n/**\n * Process monitor backing the shared {@link ProcessManagerRuntime}. Exposes the\n * live process tree (including inactive/terminated entries) via\n * {@link Process$.Monitor#processTreeAtom}.\n *\n * @category Capability\n */\nexport const ProcessMonitor = Capability$.make<Process$.Monitor>('org.dxos.app-framework.capability.process-monitor');\n\n/**\n * Services that are always available when running effects through a {@link ProcessManagerRuntime}.\n */\nexport type ProcessManagerRuntimeServices =\n | Capability$.Service\n | Plugin$.Service\n | ProcessManager$.ProcessManagerService\n | Operation$.Service\n | ProcessManager$.ProcessOperationInvoker.Service\n | ServiceResolver$.ServiceResolver;\n\n/**\n * Runtime that runs effects requiring a fixed set of capability-manager and\n * process-manager services.\n *\n * The shape mirrors {@link ManagedRuntime$.ManagedRuntime} but deliberately does\n * not expose `dispose` – lifecycle is driven by the host plugin manager.\n */\nexport interface ProcessManagerRuntime {\n runPromise<A, E>(\n effect: Effect.Effect<A, E, ProcessManagerRuntimeServices>,\n options?: { readonly signal?: AbortSignal },\n ): Promise<A>;\n runPromiseExit<A, E>(\n effect: Effect.Effect<A, E, ProcessManagerRuntimeServices>,\n options?: { readonly signal?: AbortSignal },\n ): Promise<Exit$.Exit<A, E>>;\n runFork<A, E>(\n effect: Effect.Effect<A, E, ProcessManagerRuntimeServices>,\n options?: Runtime$.RunForkOptions,\n ): Fiber$.RuntimeFiber<A, E>;\n runSync<A, E>(effect: Effect.Effect<A, E, ProcessManagerRuntimeServices>): A;\n}\n\n/**\n * @category Capability\n */\nexport const ProcessManagerRuntime = Capability$.make<ProcessManagerRuntime>(\n 'org.dxos.app-framework.capability.process-manager-runtime',\n);\n\nexport type ManagedRuntime = ManagedRuntime$.ManagedRuntime<any, any>;\n\n/**\n * @category Capability\n */\nexport const ManagedRuntime = Capability$.make<ManagedRuntime>('org.dxos.app-framework.capability.managed-runtime');\n\n//\n// Operation System Capabilities\n//\n\nexport const OperationHandler = Capability$.make<OperationHandlerSet.OperationHandlerSet>(\n 'org.dxos.app-framework.capability.operation-handler',\n);\n\nexport type UndoMapping = UndoMapping$.UndoMapping;\n\n/**\n * Undo mapping registration - contributed by plugins.\n * @category Capability\n */\nexport const UndoMapping = Capability$.make<UndoMapping[]>('org.dxos.app-framework.capability.undo-mapping');\n\n/**\n * Operation invoker backed by the process manager. Spawns a process per\n * operation invocation; see {@link ProcessManager$.ProcessOperationInvoker}.\n */\nexport type OperationInvoker = OperationInvoker$.OperationInvoker;\n\n/**\n * Operation invoker - provided by the process-manager capability.\n * @category Capability\n */\nexport const OperationInvoker = Capability$.make<OperationInvoker>(\n 'org.dxos.app-framework.capability.operation-invoker',\n);\n\nexport type UndoRegistry = UndoRegistry$.UndoRegistry;\n\n/**\n * Undo registry - provided by ProcessManagerPlugin.\n * @category Capability\n */\nexport const UndoRegistry = Capability$.make<UndoRegistry>('org.dxos.app-framework.capability.undo-registry');\n\nexport type HistoryTracker = HistoryTracker$.HistoryTracker;\n\n/**\n * History tracker - provided by ProcessManagerPlugin.\n * @category Capability\n */\nexport const HistoryTracker = Capability$.make<HistoryTracker>('org.dxos.app-framework.capability.history-tracker');\n\n//\n// Atom Capability Helpers\n//\n\n/**\n * Get the current value of an atom capability.\n * @example const settings = yield* Capabilities.getAtomValue(CommentCapabilities.Settings);\n */\nexport const getAtomValue = <T>(\n atomCapability: Capability$.InterfaceDef<Atom.Atom<T>>,\n): Effect.Effect<T, Error, Capability$.Service> =>\n Effect.gen(function* () {\n const registry = yield* Capability$.get(AtomRegistry);\n const atom = yield* Capability$.get(atomCapability);\n return registry.get(atom);\n });\n\n/**\n * Update an atom capability value (requires writable atom).\n * @example yield* Capabilities.updateAtomValue(CommentCapabilities.Settings, (s) => ({ ...s, foo: true }));\n */\nexport const updateAtomValue = <T>(\n atomCapability: Capability$.InterfaceDef<Atom.Writable<T>>,\n fn: (current: T) => T,\n): Effect.Effect<void, Error, Capability$.Service> =>\n Effect.gen(function* () {\n const registry = yield* Capability$.get(AtomRegistry);\n const atom = yield* Capability$.get(atomCapability);\n registry.set(atom, fn(registry.get(atom)));\n });\n\n/**\n * Subscribe to an atom capability.\n * @example const unsubscribe = yield* Capabilities.subscribeAtom(CommentCapabilities.Settings, (value) => ...);\n */\nexport const subscribeAtom = <T>(\n atomCapability: Capability$.InterfaceDef<Atom.Atom<T>>,\n callback: (value: T) => void,\n): Effect.Effect<() => void, Error, Capability$.Service> =>\n Effect.gen(function* () {\n const registry = yield* Capability$.get(AtomRegistry);\n const atom = yield* Capability$.get(atomCapability);\n return registry.subscribe(atom, () => callback(registry.get(atom)));\n });\n"],
5
- "mappings": ";;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,YAAYA,YAAY;AA+BjB,IAAMC,OAAOC,mBAAYC,KAAW,wCAAA;AAKpC,IAAMC,gBAAgBF,mBAAYC,KACvC,kDAAA;AAMK,IAAME,eAAeH,mBAAYC,KAAwB,iDAAA;AAWzD,IAAMG,eAAeJ,mBAAYC,KAAmB,iDAAA;AAOpD,IAAMI,YAAYL,mBAAYC,KAAgB,8CAAA;AAU9C,IAAMK,eAAeN,mBAAYC,KAAmB,6CAAA;AAOpD,IAAMM,UAAUP,mBAAYC,KAAiB,2CAAA;AAK7C,IAAMO,QAAQR,mBAAYC,KAAkC,yCAAA;AAW5D,IAAMQ,YAAYT,mBAAYC,KAA2B,8CAAA;AAmCzD,IAAMS,YAAYV,mBAAYC,KAAuB,8CAAA;AAuBrD,IAAMU,kBAAkBX,mBAAYC,KACzC,oDAAA;AAUK,IAAMW,iBAAiBZ,mBAAYC,KAAuB,mDAAA;AAuC1D,IAAMY,wBAAwBb,mBAAYC,KAC/C,2DAAA;AAQK,IAAMa,iBAAiBd,mBAAYC,KAAqB,mDAAA;AAMxD,IAAMc,mBAAmBf,mBAAYC,KAC1C,qDAAA;AASK,IAAMe,cAAchB,mBAAYC,KAAoB,gDAAA;AAYpD,IAAMgB,mBAAmBjB,mBAAYC,KAC1C,qDAAA;AASK,IAAMiB,eAAelB,mBAAYC,KAAmB,iDAAA;AAQpD,IAAMkB,iBAAiBnB,mBAAYC,KAAqB,mDAAA;AAUxD,IAAMmB,eAAe,CAC1BC,mBAEOC,WAAI,aAAA;AACT,QAAMC,WAAW,OAAOvB,mBAAYwB,IAAIrB,YAAAA;AACxC,QAAMsB,OAAO,OAAOzB,mBAAYwB,IAAIH,cAAAA;AACpC,SAAOE,SAASC,IAAIC,IAAAA;AACtB,CAAA;AAMK,IAAMC,kBAAkB,CAC7BL,gBACAM,OAEOL,WAAI,aAAA;AACT,QAAMC,WAAW,OAAOvB,mBAAYwB,IAAIrB,YAAAA;AACxC,QAAMsB,OAAO,OAAOzB,mBAAYwB,IAAIH,cAAAA;AACpCE,WAASK,IAAIH,MAAME,GAAGJ,SAASC,IAAIC,IAAAA,CAAAA,CAAAA;AACrC,CAAA;AAMK,IAAMI,gBAAgB,CAC3BR,gBACAS,aAEOR,WAAI,aAAA;AACT,QAAMC,WAAW,OAAOvB,mBAAYwB,IAAIrB,YAAAA;AACxC,QAAMsB,OAAO,OAAOzB,mBAAYwB,IAAIH,cAAAA;AACpC,SAAOE,SAASQ,UAAUN,MAAM,MAAMK,SAASP,SAASC,IAAIC,IAAAA,CAAAA,CAAAA;AAC9D,CAAA;",
6
- "names": ["Effect", "Null", "Capability$", "make", "PluginManager", "AtomRegistry", "ReactContext", "ReactRoot", "ReactSurface", "Command", "Layer", "LayerSpec", "TraceSink", "ServiceResolver", "ProcessMonitor", "ProcessManagerRuntime", "ManagedRuntime", "OperationHandler", "UndoMapping", "OperationInvoker", "UndoRegistry", "HistoryTracker", "getAtomValue", "atomCapability", "gen", "registry", "get", "atom", "updateAtomValue", "fn", "set", "subscribeAtom", "callback", "subscribe"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/core/plugin.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport * as Pipeable from 'effect/Pipeable';\n\nimport { BaseError } from '@dxos/errors';\nimport { invariant } from '@dxos/invariant';\nimport { DXN } from '@dxos/keys';\n\nimport type * as ActivationEvent from './activation-event';\nimport * as Capability from './capability';\nimport type * as PluginManager from './plugin-manager';\n\n//\n// Plugin Service Layer\n//\n\n/**\n * Effect Context.Tag for accessing PluginManager via the Effect layer system.\n * This allows lifecycle operations to access the plugin manager without having it passed as an argument.\n */\nexport class Service extends Context.Tag('@dxos/app-framework/PluginManager')<Service, PluginManager.PluginManager>() {}\n\n//\n// Lifecycle Functions\n//\n\n/**\n * Activates plugins based on the activation event.\n * Accesses the PluginManager via the Effect layer system.\n * @param event The activation event.\n * @returns Whether the activation was successful.\n */\nexport const activate = (event: ActivationEvent.ActivationEvent): Effect.Effect<boolean, Error, Service> =>\n Effect.flatMap(Service, (manager) => manager.activate(event));\n\n/**\n * Re-activates the modules that were activated by the event.\n * Accesses the PluginManager via the Effect layer system.\n * @param event The activation event.\n * @returns Whether the reset was successful.\n */\nexport const reset = (event: ActivationEvent.ActivationEvent): Effect.Effect<boolean, Error, Service> =>\n Effect.flatMap(Service, (manager) => manager.reset(event));\n\n/**\n * Shuts down the plugin manager, deactivating all active modules and clearing lifecycle state.\n * Accesses the PluginManager via the Effect layer system.\n */\nexport const shutdown = (): Effect.Effect<boolean, Error, Service> =>\n Effect.flatMap(Service, (manager) => manager.shutdown());\n\n/**\n * Computes a module ID from plugin ID and export name.\n */\nconst computeModuleId = (pluginId: string, moduleName: string): string => {\n return `${pluginId}.module.${moduleName}`;\n};\n\n/**\n * Identifier denoting a PluginModule.\n */\nexport const PluginModuleTypeId: unique symbol = Symbol.for('@dxos/app-framework/PluginModule');\nexport type PluginModuleTypeId = typeof PluginModuleTypeId;\n\n/**\n * Type guard to check if a value is a PluginModule.\n */\nexport const isPluginModule = (value: unknown): value is PluginModule => {\n return typeof value === 'object' && value !== null && PluginModuleTypeId in value;\n};\n\n/**\n * A unit of containment of modular functionality that can be provided to an application.\n * Activation of a module is async allowing for code to split and loaded lazily.\n */\nexport interface PluginModule {\n readonly [PluginModuleTypeId]: PluginModuleTypeId;\n /**\n * Unique id of the module.\n */\n id: string;\n\n /**\n * Events for which the module will be activated.\n */\n activatesOn: ActivationEvent.Events;\n\n /**\n * Events that this module fires *before* its own activation runs.\n *\n * When this module is asked to activate (via {@link activatesOn}), the\n * plugin manager first activates every event listed here, ensuring any\n * other modules that contribute to those events have completed before\n * this module's {@link activate} body executes. These events are fired\n * by the framework on this module's behalf — the module does not need\n * to wait for some other code to fire them.\n *\n * The module is marked as needing reset if a module activated by one\n * of these events is later removed.\n *\n * Read as: \"this module fires these events before [its] activation\".\n */\n firesBeforeActivation?: ActivationEvent.ActivationEvent[];\n\n /**\n * Events that this module fires *after* its own activation completes.\n *\n * Once this module's {@link activate} body has finished executing, the\n * plugin manager activates every event listed here, causing any modules\n * listening on those events to run. These events are fired by the\n * framework on this module's behalf as part of this module's lifecycle.\n *\n * Read as: \"this module fires these events after [its] activation\".\n */\n firesAfterActivation?: ActivationEvent.ActivationEvent[];\n\n /**\n * Called when the module is activated.\n * CapabilityManager is accessed via the Effect layer system (Capability.Service).\n * PluginManager is accessed via Plugin.Service.\n * @param props Optional props passed to the module.\n * @returns The capabilities of the module.\n */\n activate: (props?: any) => Effect.Effect<Capability.ModuleReturn, Error, Capability.Service | Service | never>;\n}\n\nexport type PluginModuleOptions = Omit<PluginModule, 'id' | typeof PluginModuleTypeId> & { id?: string };\n\nclass PluginModuleImpl implements PluginModule {\n readonly [PluginModuleTypeId]: PluginModuleTypeId = PluginModuleTypeId;\n readonly id: PluginModule['id'];\n readonly activatesOn: PluginModule['activatesOn'];\n readonly firesBeforeActivation?: PluginModule['firesBeforeActivation'];\n readonly firesAfterActivation?: PluginModule['firesAfterActivation'];\n readonly activate: PluginModule['activate'];\n\n constructor(options: Omit<PluginModule, typeof PluginModuleTypeId>) {\n this.id = options.id;\n this.activatesOn = options.activatesOn;\n this.firesBeforeActivation = options.firesBeforeActivation;\n this.firesAfterActivation = options.firesAfterActivation;\n this.activate = options.activate;\n }\n}\n\nexport type Meta = {\n /**\n * Bare NSID (the name portion of {@link key}, e.g. `org.dxos.plugin.example`).\n * Stable across versions; used for module-id namespacing, i18n namespaces,\n * enable/disable, and registry lookups. Derived from `key` by {@link makeMeta} —\n * do not set directly.\n */\n id: string;\n\n /**\n * Canonical identity DXN, including version when published\n * (e.g. `dxn:org.dxos.plugin.example:0.8.3`). The validated source of truth\n * from which {@link id} and {@link version} are derived.\n *\n * @example DXN.make('org.dxos.plugin.example', '0.8.3')\n */\n key: DXN.DXN;\n\n /**\n * Human-readable name.\n */\n name: string;\n\n /**\n * Semver version string of the plugin, typically the publishing package's\n * `package.json` version. Derived from the version segment of {@link key} by\n * {@link makeMeta} — do not set directly.\n */\n version?: string;\n\n /**\n * Short description of plugin functionality.\n */\n description?: string;\n\n /**\n * Name of the author or organization that created the plugin.\n */\n // TODO(burdon): DID or domain name?\n author?: string;\n\n /**\n * URL of home page.\n */\n homePage?: string;\n\n /**\n * URL of source code.\n */\n source?: string;\n\n /**\n * Relative path (inside the published package) to the plugin's bundled MDL\n * specification file — e.g. `'PLUGIN.mdl'` or `'docs/PLUGIN.mdl'`. The file\n * is shipped via the package's `files` entry and resolved by registry\n * surfaces to render an in-app viewer and/or external link.\n */\n spec?: string;\n\n /**\n * URL of screenshot.\n */\n screenshots?: string[];\n\n /**\n * Tags to help categorize the plugin.\n */\n tags?: string[];\n\n /**\n * A grep-able symbol string which can be resolved to an icon asset by @ch-ui/icons, via @ch-ui/vite-plugin-icons.\n */\n icon?: string;\n\n /**\n * Icon hue (ChromaticPalette).\n */\n iconHue?: string;\n\n /**\n * IDs of plugins this plugin functionally depends on.\n *\n * Treated as a convenience by the default `PluginManager` flow:\n * - Enabling this plugin auto-enables the transitive closure of `dependsOn`\n * (installing missing entries from the plugin registry when possible).\n * - Disabling a depended-upon plugin surfaces dependents to the caller; the\n * `PluginManager.disable` API supports an opt-in cascade.\n *\n * Not an invariant: low-level `PluginManager` APIs accept opt-outs\n * (`resolveDependencies: false`, `ignoreDependents: true`) so a caller may\n * substitute an alternative implementation that satisfies the dependent's\n * capability needs in its own way.\n */\n dependsOn?: string[];\n};\n\n/**\n * Options for {@link makeMeta}: a {@link Meta} minus the fields derived from `key`.\n * Identity and version are specified solely through the `key` DXN — `id` and\n * `version` cannot be passed directly.\n */\nexport type MakeMetaOptions = Omit<Meta, 'id' | 'version'>;\n\n/**\n * Constructs a plugin {@link Meta} from a single canonical DXN. The `key` DXN is\n * the one source of truth; `id` (bare NSID) and `version` (semver) are derived\n * from it so each datum has exactly one home and cannot drift out of sync.\n *\n * @example\n * export const meta = Plugin.makeMeta({\n * key: DXN.make('org.dxos.plugin.example', '0.8.3'),\n * name: 'Example',\n * });\n */\nexport const makeMeta = (options: MakeMetaOptions): Meta => ({\n ...options,\n id: DXN.getName(options.key),\n version: DXN.getVersion(options.key),\n});\n\n/**\n * Identifier denoting a Plugin.\n */\nexport const PluginTypeId: unique symbol = Symbol.for('@dxos/app-framework/Plugin');\nexport type PluginTypeId = typeof PluginTypeId;\n\n/**\n * Type guard to check if a value is a Plugin.\n */\nexport const isPlugin = (value: unknown): value is Plugin => {\n return typeof value === 'object' && value !== null && PluginTypeId in value;\n};\n\n/**\n * A collection of modules that are be enabled/disabled as a unit.\n * Plugins provide things such as components, state, actions, etc. to the application.\n */\n// TODO(burdon): Convert to ECHO schema.\nexport interface Plugin {\n readonly [PluginTypeId]: PluginTypeId;\n readonly meta: Readonly<Meta>;\n readonly modules: ReadonlyArray<PluginModule>;\n}\n\n/**\n * Internal implementation of Plugin.\n * @internal\n */\nclass PluginImpl implements Plugin {\n readonly [PluginTypeId]: PluginTypeId = PluginTypeId;\n\n constructor(\n private readonly _meta: Meta,\n private readonly _modules: PluginModule[],\n ) {}\n\n get meta(): Readonly<Meta> {\n return this._meta;\n }\n\n get modules(): ReadonlyArray<PluginModule> {\n return this._modules;\n }\n}\n\n/**\n * Builder interface for creating plugins incrementally.\n */\nexport interface PluginBuilder<T = void> extends Pipeable.Pipeable {\n readonly meta: Meta;\n readonly modules: ReadonlyArray<PluginModuleOptions | ((options: T) => PluginModuleOptions)>;\n addModule(moduleOptions: PluginModuleOptions | ((options: T) => PluginModuleOptions)): PluginBuilder<T>;\n}\n\n/**\n * Builder implementation for creating plugins incrementally.\n */\nclass PluginBuilderImpl<T = void> implements PluginBuilder<T> {\n readonly meta: Meta;\n private readonly _modules: Array<PluginModuleOptions | ((options: T) => PluginModuleOptions)> = [];\n\n constructor(meta: Meta) {\n this.meta = meta;\n }\n\n get modules(): ReadonlyArray<PluginModuleOptions | ((options: T) => PluginModuleOptions)> {\n return this._modules;\n }\n\n addModule(moduleOptions: PluginModuleOptions | ((options: T) => PluginModuleOptions)): PluginBuilder<T> {\n this._modules.push(moduleOptions);\n return this;\n }\n\n pipe() {\n // eslint-disable-next-line prefer-rest-params\n return Pipeable.pipeArguments(this, arguments);\n }\n}\n\n/**\n * Creates a new PluginBuilder to start building a plugin.\n */\nexport const define = <T = void>(meta: Meta): PluginBuilder<T> => new PluginBuilderImpl<T>(meta);\n\n/**\n * Adds a module to a plugin builder.\n * Supports both pipeline and direct call styles.\n * Modules can be either PluginModuleOptions or functions that receive options.\n */\nexport function addModule<T>(\n moduleOptions: PluginModuleOptions | ((options: T) => PluginModuleOptions),\n): (builder: PluginBuilder<T>) => PluginBuilder<T>;\nexport function addModule<T>(\n builder: PluginBuilder<T>,\n moduleOptions: PluginModuleOptions | ((options: T) => PluginModuleOptions),\n): PluginBuilder<T>;\nexport function addModule<T>(\n moduleOptionsOrBuilder: PluginModuleOptions | ((options: T) => PluginModuleOptions) | PluginBuilder<T>,\n moduleOptions?: PluginModuleOptions | ((options: T) => PluginModuleOptions),\n): ((builder: PluginBuilder<T>) => PluginBuilder<T>) | PluginBuilder<T> {\n // If second arg is provided, it's the direct call style: addModule(builder, moduleOptions)\n if (moduleOptions !== undefined) {\n return (moduleOptionsOrBuilder as PluginBuilder<T>).addModule(moduleOptions);\n }\n // Otherwise it's pipeline style: addModule(moduleOptions) returns a function\n const moduleOpts = moduleOptionsOrBuilder as PluginModuleOptions | ((options: T) => PluginModuleOptions);\n return (builder: PluginBuilder<T>) => builder.addModule(moduleOpts);\n}\n\nexport type PluginFactory<T = void> = ((options: T) => Plugin) & { meta: Meta };\n\n/**\n * Resolves a module from either PluginModuleOptions or a function that returns PluginModuleOptions.\n */\nconst resolveModule = (\n meta: Meta,\n module: PluginModuleOptions | ((options: any) => PluginModuleOptions),\n options?: any,\n): PluginModuleImpl => {\n const moduleOptions = typeof module === 'function' ? module(options) : module;\n const pluginName = meta.id;\n const id = Option.fromNullable(moduleOptions.id).pipe(\n Option.match({\n onNone: () => {\n const exportName = Capability.getModuleTag(moduleOptions.activate);\n invariant(exportName, `Plugin module missing name. Plugin: ${meta.id}`);\n return computeModuleId(pluginName, exportName);\n },\n onSome: (id) => computeModuleId(pluginName, id),\n }),\n );\n return new PluginModuleImpl({ ...moduleOptions, id });\n};\n\n/**\n * Creates a Plugin from a builder.\n * Supports both pipeline and direct call styles.\n * Always returns a factory function (options: T) => Plugin.\n * When T is void, the function takes no arguments: () => Plugin.\n */\nexport function make<T>(builder: PluginBuilder<T>): PluginFactory<T>;\nexport function make<T>(builder: PluginBuilder<T>): PluginFactory<T> {\n const meta = builder.meta;\n // `dependsOn` entries and `id` are both bare NSIDs, so compare directly.\n invariant(!meta.dependsOn?.includes(meta.id), `Plugin ${meta.id} declares itself as a dependency.`);\n\n const factory = (options: T) => {\n const modules = builder.modules.map((module) => resolveModule(meta, module, options));\n return new PluginImpl(meta, modules);\n };\n\n return Object.assign(factory, { meta });\n}\n\n//\n// Lazy plugin loading\n//\n\n/**\n * Symbol used to tag lazy plugin stubs with their loader closure.\n * Hidden from enumeration so plugin manager iteration / serialization paths\n * don't trip over it.\n */\nconst LazyTag: unique symbol = Symbol.for('@dxos/app-framework/Plugin/Lazy');\n\n/**\n * Async loader for a lazy plugin's real implementation.\n * The default export of the loaded module must be a `PluginFactory<T>` —\n * i.e. the same shape `Plugin.make` produces.\n */\nexport type LazyLoader<T = void> = () => Promise<{ default: PluginFactory<T> }>;\n\n/** Internal: payload carried on a lazy stub. */\ntype LazyPayload = { loader: LazyLoader<any>; options: unknown };\n\n/**\n * Defines a lazy plugin whose body is loaded on first enable.\n *\n * The returned factory produces a stub `Plugin` that exposes `meta`\n * synchronously (so callers can read `Plugin.meta.id` for free) but defers\n * loading the real plugin's modules until the manager calls\n * `Plugin.resolveLazy`. This lets the plugin's main entry point ship as a\n * tiny meta-only chunk — the heavy capabilities, schema, React surfaces,\n * etc. live behind the dynamic `import()` and become a separate Rollup\n * chunk that is only fetched when the plugin is enabled.\n *\n * @example\n * ```ts\n * // plugin-markdown/src/index.ts\n * import { Plugin } from '@dxos/app-framework';\n * import { meta } from './meta';\n *\n * export const MarkdownPlugin = Plugin.lazy(meta, () => import('./MarkdownPlugin'));\n *\n * // plugin-markdown/src/MarkdownPlugin.tsx\n * export const MarkdownPlugin = Plugin.define(meta).pipe(...heavy modules..., Plugin.make);\n * export default MarkdownPlugin;\n * ```\n */\nexport const lazy = <T = void>(meta: Meta, loader: LazyLoader<T>): PluginFactory<T> => {\n const factory = (options: T): Plugin => {\n const stub = new PluginImpl(meta, []);\n Object.defineProperty(stub, LazyTag, {\n value: { loader, options } satisfies LazyPayload,\n enumerable: false,\n });\n return stub;\n };\n return Object.assign(factory, { meta });\n};\n\n/**\n * Type guard for lazy plugin stubs produced by {@link lazy}.\n */\nexport const isLazy = (plugin: Plugin): boolean => LazyTag in plugin;\n\n/**\n * Tagged error for failures during lazy plugin resolution. `context.id` is\n * the lazy plugin's `meta.id`; `context.reason` discriminates the failure\n * mode (`'load-failed' | 'missing-default' | 'invalid-plugin' |\n * 'meta-mismatch'`) so callers can route on it.\n */\nexport class LazyPluginError extends BaseError.extend('LazyPluginError', 'Failed to resolve lazy plugin') {}\n\n/**\n * Tagged error for plugin-level dependency resolution failures.\n *\n * `context.id` is the plugin id the manager was acting on. `context.reason`\n * discriminates the failure mode:\n * - `'missing'` — declared dep is neither registered nor in the catalog.\n * `context.missing` lists offending ids.\n * - `'install-failed'` — dep was found in the catalog but `add()` failed.\n * `cause` carries the original error.\n * - `'cycle'` — closure walk detected a cycle. `context.path` is the cycle path.\n * - `'core-dependent'` — cascade-disable would have to disable a core plugin.\n * `context.coreDependent` is the blocking id.\n */\nexport class PluginDependencyError extends BaseError.extend(\n 'PluginDependencyError',\n 'Plugin dependency resolution failed',\n) {}\n\n/**\n * Resolves a lazy plugin stub to its real plugin.\n * Returns the plugin unchanged if it is not lazy. Failures surface as\n * {@link LazyPluginError} with `context.reason` indicating the failure mode\n * and (for loader failures) `cause` set to the original error.\n */\nexport const resolveLazy = (plugin: Plugin): Effect.Effect<Plugin, LazyPluginError> =>\n Effect.gen(function* () {\n if (!isLazy(plugin)) {\n return plugin;\n }\n const id = plugin.meta.id;\n const { loader, options } = (plugin as unknown as { [LazyTag]: LazyPayload })[LazyTag];\n const mod = yield* Effect.tryPromise({\n try: loader,\n catch: (error) => new LazyPluginError({ context: { id, reason: 'load-failed' }, cause: error }),\n });\n if (!mod || typeof mod.default !== 'function') {\n return yield* Effect.fail(new LazyPluginError({ context: { id, reason: 'missing-default' } }));\n }\n const result = mod.default(options);\n if (!isPlugin(result)) {\n return yield* Effect.fail(new LazyPluginError({ context: { id, reason: 'invalid-plugin' } }));\n }\n if (result.meta.id !== id) {\n return yield* Effect.fail(\n new LazyPluginError({ context: { id, reason: 'meta-mismatch', returnedId: result.meta.id } }),\n );\n }\n return result;\n });\n"],
5
- "mappings": ";;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;AAIA,YAAYA,aAAa;AACzB,YAAYC,YAAY;AACxB,YAAYC,YAAY;AACxB,YAAYC,cAAc;AAE1B,SAASC,iBAAiB;AAC1B,SAASC,iBAAiB;AAC1B,SAASC,WAAW;AAMpB,IAAE,eAAA;AAUA,IAAA,UAAA,cAAA,YAAA,mCAAA,EAAA,EAAA;AACF;;;;AAiCA,IAAA,kBAAA,CAAA,UAAA,eAAA;AAEA,SAAA,GAAA,QAAA,WAAA,UAAA;;;AAWE,IAAA,iBAAA,CAAA,UAAA;AA2DF,SAAMC,OAAAA,UAAAA,YAAAA,UAAAA,QAAAA,sBAAAA;;IAEKC,yBAAuB;EACvBC,CAAAA,kBAAyC,IAAA;EACzCC;EACAC;EACAC;EAET;;cAEOH,SAAW;AAChB,SAAKC,KAAAA,QAAAA;AACL,SAAKC,cAAAA,QAAoB;AACzB,SAAKC,wBAAmBA,QAAQ;AAClC,SAAA,uBAAA,QAAA;AACF,SAAA,WAAA,QAAA;EAyGA;;AAaEJ,IAAQK,WAAQC,CAAAA,aAAW;EAC3BC,GAAAA;EACC,IAAA,IAAA,QAAA,QAAA,GAAA;EAEH,SAAA,IAAA,WAAA,QAAA,GAAA;;;AAWE,IAAA,WAAA,CAAA,UAAA;AAaF,SAAA,OAAA,UAAA,YAAA,UAAA,QAAA,gBAAA;;;EAKW;EAET;eACmBC,IAAAA;cACAC,OAAAA,UAAAA;AAChB,SAAA,QAAA;AAECC,SAAAA,WAAuB;;EAE3B,IAAA,OAAA;AAEIC,WAAAA,KAAuC;;EAE3C,IAAA,UAAA;AACF,WAAA,KAAA;EAWA;;AAKmBF,IAAAA,oBAAAA,MAAkF;EAEnG;aACOC,CAAAA;EACP,YAAA,MAAA;AAEIC,SAAAA,OAAsF;;EAE1F,IAAA,UAAA;AAEAC,WAAUC,KAAAA;;YAER,eAAW;AACb,SAAA,SAAA,KAAA,aAAA;AAEAC,WAAO;;SAEL;AAEJ,WAAA,uBAAA,MAAA,SAAA;EAEA;;AAqBE,IAAA,SAAA,CAAA,SAAA,IAAA,kBAAA,IAAA;AACA,SAAID,UAAkBE,wBAAW,eAAA;AAEjC,MAAA,kBAAA,QAAA;AACA,WAAA,uBAAA,UAAA,aAAA;EACA;AAEF,QAAA,aAAA;AAIA,SAAA,CAAA,YAAA,QAAA,UAAA,UAAA;;AASE,IAAMC,gBAAaN,CAAAA,MAAO,QAAA,YAAA;AAC1B,QAAMV,gBAAYiB,OAAAA,WAAaJ,aAAsB,OACnDK,OAAa,IAAA;QACXC,aAAQ,KAAA;aACAC,oBAAaC,cAAWC,EAAAA,EAAAA,KAAaT,aAAcT;YACzDmB,MAAAA;AACA,YAAA,aAAuBP,aAAYI,cAAAA,QAAAA;AACrC,gBAAA,YAAA,uCAAA,KAAA,EAAA,IAAA,EAAA,YAAA,YAAA,GAAA,cAAA,GAAA,KAAA,GAAA,QAAA,GAAA,CAAA,cAAA,kDAAA,EAAA,CAAA;AACAI,aAASxB,gBAAOyB,YAAgBT,UAAYhB;IAC9C;IAEF,QAAWD,CAAAA,QAAAA,gBAAiB,YAAAC,GAAA;;SAAoBA,IAAAA,iBAAAA;IAAG,GAAA;IACrD;EASA,CAAA;;AAEE,SAAA,KAAA,SAAA;AACAuB,QAAAA,OAAWb,QAAKgB;YAGRf,CAAAA,KAAAA,WAAUgB,SAAgBC,KAAKC,EAAAA,GAAAA,UAAWC,KAAAA,EAAAA,qCAA4BxB,EAAAA,YAAAA,YAAAA,GAAAA,cAAAA,GAAAA,KAAAA,GAAAA,MAAAA,GAAAA,CAAAA,sCAAAA,sDAAAA,EAAAA,CAAAA;QAC5E,UAAWyB,CAAAA,YAAWrB;AACxB,UAAA,UAAA,QAAA,QAAA,IAAA,CAAA,WAAA,cAAA,MAAA,QAAA,OAAA,CAAA;AAEA,WAAOsB,IAAOC,WAAOC,MAAS,OAAA;;AAAO,SAAA,OAAA,OAAA,SAAA;IACvC;EAEE,CAAA;AACF;;AA8CI,IAAMC,OAAO,CAAIJ,MAAAA,WAAWrB;QAC5BsB,UAAOI,CAAAA,YAAeD;UACpBE,OAAO,IAAA,WAAA,MAAA,CAAA,CAAA;0BAAEC,MAAAA,SAAAA;aAAQhC;QAAQ;QACzBiC;MACF;MACA,YAAOJ;IACT,CAAA;AACA,WAAOH;;AAA8B,SAAA,OAAA,OAAA,SAAA;IACrC;EAEF,CAAA;;;AAaA,IAAA,kBAAA,cAAA,UAAA,OAAA,mBAAA,+BAAA,EAAA;;AAkBA,IAAA,wBAAA,cAAA,UAAA,OAAA,yBAAA,qCAAA,EAAA;;IASM,cAAOQ,CAAAA,WAAAA,WAAAA,aAAAA;AACT,MAAA,CAAA,OAAA,MAAA,GAAA;AACA,WAAWA;EACX;AACA,QAAMC,KAAAA,OAAM,KAAOC;QACjBC,EAAAA,QAAKL,QAAAA,IAAAA,OAAAA,OAAAA;QACLM,MAAQC,OAAcC,kBAAAA;;sBAA6B9C,IAAAA,gBAAAA;eAAI+C;QAAsB;QAAGC,QAAOH;MAAM;MAC/F,OAAA;IACKJ,CAAAA;;cAC6CQ,OAAS,IAAA,YAAA,YAAA;kBAAEjD,YAAAA,IAAAA,gBAAAA;eAAI+C;QAA0B;QAAE,QAAA;MAC7F;IACA,CAAA,CAAMG;EACN;QACE,SAAO,IAAOR,QAAOS,OAASL;gBAAkBG,MAAS,GAAA;kBAAEjD,YAAAA,IAAAA,gBAAAA;eAAI+C;QAAyB;QAAE,QAAA;MAC5F;IACIG,CAAAA,CAAAA;;aAEsBD,KAAAA,OAAS,IAAA;kBAAEjD,YAAAA,IAAAA,gBAAAA;eAAI+C;QAAyBK;QAA2B,QAAA;QAAE,YAAA,OAAA,KAAA;MAE/F;IACA,CAAA,CAAA;EACC;;;",
6
- "names": ["Context", "Effect", "Option", "Pipeable", "BaseError", "invariant", "DXN", "PluginModuleImpl", "id", "activatesOn", "firesBeforeActivation", "firesAfterActivation", "activate", "getName", "options", "version", "_meta", "_modules", "meta", "modules", "addModule", "moduleOptions", "pipe", "undefined", "pluginName", "fromNullable", "Option", "onNone", "exportName", "Capability", "getModuleTag", "invariant", "onSome", "computeModuleId", "dependsOn", "builder", "map", "module", "resolveModule", "PluginImpl", "Object", "assign", "factory", "stub", "defineProperty", "value", "loader", "enumerable", "plugin", "mod", "Effect", "try", "catch", "error", "LazyPluginError", "reason", "cause", "context", "result", "fail", "returnedId"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/core/edge-registry-plugin-provider.ts"],
4
- "sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Context } from '@dxos/context';\nimport { type EdgeHttpClient } from '@dxos/edge-client';\nimport { type PluginEntry } from '@dxos/protocols';\n\nimport * as Registry from './registry';\n\n/**\n * Maps a wire-format `PluginEntry` (from `@dxos/protocols`) to a\n * `Registry.Plugin` (the app-framework domain type).\n *\n * This is the only translation seam between the two independently-defined type\n * hierarchies — fields are mapped explicitly, with no shared type between them.\n */\nconst toRegistryPlugin = (entry: PluginEntry): Registry.Plugin => ({\n id: entry.meta.id,\n name: entry.meta.name,\n description: entry.meta.description,\n homePage: entry.meta.homePage,\n source: entry.meta.source,\n screenshots: entry.meta.screenshots ? [...entry.meta.screenshots] : undefined,\n tags: entry.meta.tags ? [...entry.meta.tags] : undefined,\n icon: entry.meta.icon,\n iconHue: entry.meta.iconHue,\n moduleUrl: entry.moduleUrl,\n repo: entry.repo,\n version: entry.releaseTag,\n});\n\n/**\n * Implements `Registry.PluginProvider` against the Edge `/registry` HTTP endpoints.\n *\n * Lives in app-framework (rather than the more obvious home of `@dxos/edge-client`)\n * because edge-client doesn't depend on app-framework; siting the implementation\n * here keeps the dependency arrow `app-framework → edge-client` and avoids a\n * cycle. The class needs only the public `EdgeHttpClient` type, so a one-way\n * type-import is enough.\n *\n * `listVersions` is currently a stub: it returns the single latest version derived\n * from the cached plugin list, so the host's version picker has something to render.\n * The wire contract for the real endpoint already exists — see\n * `GetPluginVersionsResponseBodySchema` in `@dxos/protocols/edge/registry` and\n * {@link EdgeHttpClient.getRegistryPluginVersions} — so once Edge ships\n * `GET /registry/plugins/:repo/versions`, swap this stub for a call to\n * `this._client.getRegistryPluginVersions(...)` and map each entry through a\n * `toRegistryPluginVersion` helper (mirror of {@link toRegistryPlugin}).\n */\nexport class EdgeRegistryPluginProvider implements Registry.PluginProvider {\n // Cached on first load so getPlugin/listVersions can resolve without re-fetching.\n #cachedPlugins: readonly Registry.Plugin[] = [];\n\n constructor(private readonly _client: EdgeHttpClient) {}\n\n listPlugins(): Effect.Effect<readonly Registry.Plugin[], Error> {\n return Effect.tryPromise({\n try: () => this._client.getRegistryPlugins(Context.default()),\n catch: (error) => (error instanceof Error ? error : new Error(String(error))),\n }).pipe(\n Effect.map((body) => {\n const plugins = body.plugins.filter((entry) => entry.health === 'ok').map(toRegistryPlugin);\n this.#cachedPlugins = plugins;\n return plugins;\n }),\n );\n }\n\n listVersions(repo: string): Effect.Effect<readonly Registry.PluginVersion[], Error> {\n // Stub: return only the currently-known version until Edge implements the versions endpoint.\n const plugin = this.#cachedPlugins.find((candidate) => candidate.repo === repo);\n if (!plugin) {\n return Effect.fail(new Error(`Plugin not found in catalog: ${repo}`));\n }\n const version: Registry.PluginVersion = { tag: plugin.version, moduleUrl: plugin.moduleUrl };\n return Effect.succeed([version]);\n }\n\n getPlugin(repo: string, version?: string): Effect.Effect<Registry.Plugin, Error> {\n const plugin = this.#cachedPlugins.find((p) => p.repo === repo);\n if (!plugin) {\n return Effect.fail(new Error(`Plugin not found in catalog: ${repo}`));\n }\n if (version && version !== plugin.version) {\n return Effect.fail(new Error(`Version ${version} not available for ${repo}; only ${plugin.version} is cached`));\n }\n return Effect.succeed(plugin);\n }\n}\n"],
5
- "mappings": ";AAIA,YAAYA,YAAY;AAExB,SAASC,eAAe;AAMxB,IAAA,eAAA;IASEC,mBAAqB,CAAA,WAAA;EACrBC,IAAAA,MAAAA,KAAaC;EACbC,MAAAA,MAAUD,KAAME;EAChBC,aAAQH,MAAWG,KAAAA;EACnBC,UAAAA,MAAaJ,KAAME;UAAuBF,MAAME,KAAKE;eAAeC,MAAAA,KAAAA,cAAAA;IACpEC,GAAMN,MAAME,KAAKI;;QAA8BD,MAAAA,KAAAA,OAAAA;IAC/CE,GAAMP,MAAME,KAAKK;EACjBC,IAAAA;EACAC,MAAAA,MAAWT,KAAMS;EACjBC,SAAMV,MAAMU,KAAI;EAChBC,WAASX,MAAMY;EACjB,MAAA,MAAA;EAEA,SAAA,MAAA;;AAmBE,IAAA,6BAAA,MAAA;EACA;;mBAE6BC,CAAAA;EAA0B,YAAA,SAAA;AAEvDC,SAAAA,UAAgE;;gBAEvD;WACGC,kBAAWA;MAClBC,KACDpB,MAAAA,KAAU,QAAEqB,mBAAAA,QAAAA,QAAAA,QAAAA,EAAAA,YAAAA,YAAAA,GAAAA,cAAAA,GAAAA,GAAAA,CAAAA,CAAAA;MACV,OAAMC,CAAAA,UAAUD,iBAAaE,QAAQnB,QAAUA,IAAMoB,MAAM,OAAK,KAAS,CAACC;YACrE,WAAA,CAAA,SAAiBH;AACtB,YAAA,UAAOA,KAAAA,QAAAA,OAAAA,CAAAA,UAAAA,MAAAA,WAAAA,IAAAA,EAAAA,IAAAA,gBAAAA;AACT,WAAA,iBAAA;AAEJ,aAAA;IAEAI,CAAAA,CAAAA;;eAEQC,MAAS;UAEb,SAAO3B,KAAO4B,eAAgB,KAAA,CAAA,cAAA,UAA6B,SAAQ,IAAA;AACrE,QAAA,CAAA,QAAA;AACA,aAAwC,YAAA,IAAA,MAAA,gCAAA,IAAA,EAAA,CAAA;;UAAuBf,UAAWc;MAAiB,KAAA,OAAA;MAC3F,WAAcE,OAAQ;;WAAS,eAAA;MACjC;IAEAC,CAAAA;;YAEOH,MAAAA,SAAQ;UACX,SAAO3B,KAAO4B,eAAgB,KAAA,CAAA,MAAA,EAAA,SAAA,IAAA;AAChC,QAAA,CAAA,QAAA;AACIb,aAAWA,YAAAA,IAAYY,MAAAA,gCAAgB,IAAA,EAAA,CAAA;;AAE3C,QAAA,WAAA,YAAA,OAAA,SAAA;AACA,aAAcE,YAAQF,IAAAA,MAAAA,WAAAA,OAAAA,sBAAAA,IAAAA,UAAAA,OAAAA,OAAAA,YAAAA,CAAAA;IACxB;AACF,WAAA,eAAA,MAAA;;;",
6
- "names": ["Effect", "Context", "name", "description", "entry", "homePage", "meta", "source", "screenshots", "undefined", "tags", "icon", "iconHue", "moduleUrl", "repo", "version", "releaseTag", "_client", "listPlugins", "error", "pipe", "body", "plugins", "filter", "health", "toRegistryPlugin", "listVersions", "plugin", "fail", "succeed", "getPlugin"]
7
- }