@dxos/app-framework 0.7.5-main.9d2a38b → 0.7.5-main.e9bb01b

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 (181) hide show
  1. package/dist/lib/browser/app-graph-builder-F7VZ6LRN.mjs +137 -0
  2. package/dist/lib/browser/app-graph-builder-F7VZ6LRN.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-GNLU3GAU.mjs → chunk-ATRNTMSS.mjs} +623 -819
  4. package/dist/lib/browser/chunk-ATRNTMSS.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-LDJ3T4V3.mjs +32 -0
  6. package/dist/lib/browser/chunk-LDJ3T4V3.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-WS6SU6HI.mjs +285 -0
  8. package/dist/lib/browser/chunk-WS6SU6HI.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +57 -74
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/intent-dispatcher-E6J7E5Y5.mjs +11 -0
  12. package/dist/lib/browser/intent-dispatcher-E6J7E5Y5.mjs.map +7 -0
  13. package/dist/lib/browser/intent-resolver-XLE4L3LS.mjs +38 -0
  14. package/dist/lib/browser/intent-resolver-XLE4L3LS.mjs.map +7 -0
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/store-QU2IKFAI.mjs +19 -0
  17. package/dist/lib/browser/store-QU2IKFAI.mjs.map +7 -0
  18. package/dist/lib/browser/testing/index.mjs +10 -3
  19. package/dist/lib/browser/testing/index.mjs.map +3 -3
  20. package/dist/lib/browser/worker.mjs +77 -0
  21. package/dist/lib/browser/worker.mjs.map +7 -0
  22. package/dist/lib/node/app-graph-builder-JGBADFF7.cjs +146 -0
  23. package/dist/lib/node/app-graph-builder-JGBADFF7.cjs.map +7 -0
  24. package/dist/lib/node/chunk-QLVQ6PND.cjs +58 -0
  25. package/dist/lib/node/chunk-QLVQ6PND.cjs.map +7 -0
  26. package/dist/lib/node/chunk-WKC6YMEQ.cjs +1433 -0
  27. package/dist/lib/node/chunk-WKC6YMEQ.cjs.map +7 -0
  28. package/dist/lib/node/chunk-WRWRZKZU.cjs +308 -0
  29. package/dist/lib/node/chunk-WRWRZKZU.cjs.map +7 -0
  30. package/dist/lib/node/index.cjs +106 -118
  31. package/dist/lib/node/index.cjs.map +4 -4
  32. package/dist/lib/node/intent-dispatcher-CFBKDZQR.cjs +32 -0
  33. package/dist/lib/node/intent-dispatcher-CFBKDZQR.cjs.map +7 -0
  34. package/dist/lib/node/intent-resolver-3TKCXP4S.cjs +45 -0
  35. package/dist/lib/node/intent-resolver-3TKCXP4S.cjs.map +7 -0
  36. package/dist/lib/node/meta.json +1 -1
  37. package/dist/lib/node/store-4QMUUU2A.cjs +34 -0
  38. package/dist/lib/node/store-4QMUUU2A.cjs.map +7 -0
  39. package/dist/lib/node/testing/index.cjs +15 -8
  40. package/dist/lib/node/testing/index.cjs.map +3 -3
  41. package/dist/lib/node/worker.cjs +99 -0
  42. package/dist/lib/node/worker.cjs.map +7 -0
  43. package/dist/lib/node-esm/app-graph-builder-2QEX57NX.mjs +138 -0
  44. package/dist/lib/node-esm/app-graph-builder-2QEX57NX.mjs.map +7 -0
  45. package/dist/lib/node-esm/{chunk-KPMTPXQI.mjs → chunk-44J2VZBB.mjs} +623 -819
  46. package/dist/lib/node-esm/chunk-44J2VZBB.mjs.map +7 -0
  47. package/dist/lib/node-esm/chunk-CNJYZNSL.mjs +34 -0
  48. package/dist/lib/node-esm/chunk-CNJYZNSL.mjs.map +7 -0
  49. package/dist/lib/node-esm/chunk-HTLXL32I.mjs +286 -0
  50. package/dist/lib/node-esm/chunk-HTLXL32I.mjs.map +7 -0
  51. package/dist/lib/node-esm/index.mjs +57 -74
  52. package/dist/lib/node-esm/index.mjs.map +4 -4
  53. package/dist/lib/node-esm/intent-dispatcher-LDQGDZ62.mjs +12 -0
  54. package/dist/lib/node-esm/intent-dispatcher-LDQGDZ62.mjs.map +7 -0
  55. package/dist/lib/node-esm/intent-resolver-7VJWN67U.mjs +39 -0
  56. package/dist/lib/node-esm/intent-resolver-7VJWN67U.mjs.map +7 -0
  57. package/dist/lib/node-esm/meta.json +1 -1
  58. package/dist/lib/node-esm/store-VWDAYUQY.mjs +20 -0
  59. package/dist/lib/node-esm/store-VWDAYUQY.mjs.map +7 -0
  60. package/dist/lib/node-esm/testing/index.mjs +10 -3
  61. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  62. package/dist/lib/node-esm/worker.mjs +78 -0
  63. package/dist/lib/node-esm/worker.mjs.map +7 -0
  64. package/dist/types/src/App.d.ts.map +1 -1
  65. package/dist/types/src/common/capabilities.d.ts +63 -110
  66. package/dist/types/src/common/capabilities.d.ts.map +1 -1
  67. package/dist/types/src/common/events.d.ts +8 -1
  68. package/dist/types/src/common/events.d.ts.map +1 -1
  69. package/dist/types/src/common/file.d.ts +1 -1
  70. package/dist/types/src/common/file.d.ts.map +1 -1
  71. package/dist/types/src/common/graph.d.ts +2 -2
  72. package/dist/types/src/common/graph.d.ts.map +1 -1
  73. package/dist/types/src/common/index.d.ts +0 -1
  74. package/dist/types/src/common/index.d.ts.map +1 -1
  75. package/dist/types/src/common/layout.d.ts +204 -121
  76. package/dist/types/src/common/layout.d.ts.map +1 -1
  77. package/dist/types/src/common/surface.d.ts +3 -3
  78. package/dist/types/src/common/surface.d.ts.map +1 -1
  79. package/dist/types/src/common/translations.d.ts +7 -7
  80. package/dist/types/src/common/translations.d.ts.map +1 -1
  81. package/dist/types/src/core/capabilities.d.ts +6 -2
  82. package/dist/types/src/core/capabilities.d.ts.map +1 -1
  83. package/dist/types/src/core/manager.d.ts +2 -9
  84. package/dist/types/src/core/manager.d.ts.map +1 -1
  85. package/dist/types/src/core/plugin.d.ts +5 -2
  86. package/dist/types/src/core/plugin.d.ts.map +1 -1
  87. package/dist/types/src/playground/generator/Toolbar.d.ts.map +1 -1
  88. package/dist/types/src/playground/generator/generator.d.ts +2 -0
  89. package/dist/types/src/playground/generator/generator.d.ts.map +1 -1
  90. package/dist/types/src/playground/generator/plugin.d.ts.map +1 -1
  91. package/dist/types/src/playground/logger/Toolbar.d.ts.map +1 -1
  92. package/dist/types/src/playground/logger/plugin.d.ts.map +1 -1
  93. package/dist/types/src/playground/logger/schema.d.ts +1 -1
  94. package/dist/types/src/playground/logger/schema.d.ts.map +1 -1
  95. package/dist/types/src/plugin-intent/IntentPlugin.d.ts.map +1 -1
  96. package/dist/types/src/plugin-intent/actions.d.ts +1 -1
  97. package/dist/types/src/plugin-intent/actions.d.ts.map +1 -1
  98. package/dist/types/src/plugin-intent/index.d.ts +0 -1
  99. package/dist/types/src/plugin-intent/index.d.ts.map +1 -1
  100. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts +27 -20
  101. package/dist/types/src/plugin-intent/intent-dispatcher.d.ts.map +1 -1
  102. package/dist/types/src/plugin-intent/intent.d.ts +3 -3
  103. package/dist/types/src/plugin-intent/intent.d.ts.map +1 -1
  104. package/dist/types/src/plugin-settings/SettingsPlugin.d.ts.map +1 -1
  105. package/dist/types/src/plugin-settings/actions.d.ts +11 -1
  106. package/dist/types/src/plugin-settings/actions.d.ts.map +1 -1
  107. package/dist/types/src/plugin-settings/app-graph-builder.d.ts +197 -0
  108. package/dist/types/src/plugin-settings/app-graph-builder.d.ts.map +1 -0
  109. package/dist/types/src/plugin-settings/intent-resolver.d.ts +4 -0
  110. package/dist/types/src/plugin-settings/intent-resolver.d.ts.map +1 -0
  111. package/dist/types/src/plugin-settings/store.d.ts +5 -0
  112. package/dist/types/src/plugin-settings/store.d.ts.map +1 -0
  113. package/dist/types/src/plugin-settings/translations.d.ts +11 -0
  114. package/dist/types/src/plugin-settings/translations.d.ts.map +1 -0
  115. package/dist/types/src/{plugin-intent → react}/IntentContext.d.ts +1 -1
  116. package/dist/types/src/react/IntentContext.d.ts.map +1 -0
  117. package/dist/types/src/react/Surface.d.ts.map +1 -1
  118. package/dist/types/src/react/Surface.stories.d.ts +16 -0
  119. package/dist/types/src/react/Surface.stories.d.ts.map +1 -0
  120. package/dist/types/src/react/common.d.ts +12 -0
  121. package/dist/types/src/react/common.d.ts.map +1 -0
  122. package/dist/types/src/react/index.d.ts +2 -0
  123. package/dist/types/src/react/index.d.ts.map +1 -1
  124. package/dist/types/src/react/useIntentResolver.d.ts +3 -0
  125. package/dist/types/src/react/useIntentResolver.d.ts.map +1 -0
  126. package/dist/types/src/testing/withPluginManager.d.ts +1 -1
  127. package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
  128. package/dist/types/src/worker.d.ts +4 -0
  129. package/dist/types/src/worker.d.ts.map +1 -0
  130. package/package.json +26 -20
  131. package/project.json +3 -3
  132. package/src/App.tsx +15 -14
  133. package/src/common/capabilities.ts +20 -11
  134. package/src/common/events.ts +10 -1
  135. package/src/common/file.ts +1 -1
  136. package/src/common/graph.ts +2 -2
  137. package/src/common/index.ts +0 -1
  138. package/src/common/layout.ts +194 -126
  139. package/src/common/surface.ts +2 -2
  140. package/src/common/translations.ts +7 -8
  141. package/src/core/capabilities.ts +16 -7
  142. package/src/core/manager.test.ts +22 -73
  143. package/src/core/manager.ts +105 -91
  144. package/src/core/plugin.ts +6 -3
  145. package/src/playground/debug/plugin.ts +1 -1
  146. package/src/playground/generator/Toolbar.tsx +11 -11
  147. package/src/playground/generator/generator.ts +25 -0
  148. package/src/playground/generator/plugin.ts +6 -1
  149. package/src/playground/layout/plugin.ts +1 -1
  150. package/src/playground/logger/Toolbar.tsx +2 -1
  151. package/src/playground/logger/plugin.ts +6 -3
  152. package/src/playground/logger/schema.ts +1 -1
  153. package/src/plugin-intent/IntentPlugin.tsx +3 -43
  154. package/src/plugin-intent/actions.ts +1 -1
  155. package/src/plugin-intent/index.ts +0 -1
  156. package/src/plugin-intent/intent-dispatcher.test.ts +48 -29
  157. package/src/plugin-intent/intent-dispatcher.ts +76 -41
  158. package/src/plugin-intent/intent.ts +5 -5
  159. package/src/plugin-settings/SettingsPlugin.ts +19 -13
  160. package/src/plugin-settings/actions.ts +11 -1
  161. package/src/plugin-settings/app-graph-builder.ts +122 -0
  162. package/src/plugin-settings/intent-resolver.ts +28 -0
  163. package/src/plugin-settings/store.ts +20 -0
  164. package/src/plugin-settings/translations.ts +17 -0
  165. package/src/{plugin-intent → react}/IntentContext.tsx +2 -2
  166. package/src/react/Surface.stories.tsx +96 -0
  167. package/src/react/Surface.tsx +11 -8
  168. package/src/react/common.ts +12 -0
  169. package/src/react/index.ts +2 -0
  170. package/src/react/useIntentResolver.ts +22 -0
  171. package/src/testing/withPluginManager.tsx +11 -3
  172. package/src/worker.ts +11 -0
  173. package/tsconfig.json +3 -3
  174. package/dist/lib/browser/chunk-GNLU3GAU.mjs.map +0 -7
  175. package/dist/lib/node/chunk-FBA4BB3J.cjs +0 -1639
  176. package/dist/lib/node/chunk-FBA4BB3J.cjs.map +0 -7
  177. package/dist/lib/node-esm/chunk-KPMTPXQI.mjs.map +0 -7
  178. package/dist/types/src/common/navigation.d.ts +0 -241
  179. package/dist/types/src/common/navigation.d.ts.map +0 -1
  180. package/dist/types/src/plugin-intent/IntentContext.d.ts.map +0 -1
  181. package/src/common/navigation.ts +0 -199
@@ -79,17 +79,20 @@ export const contributes = <T>(
79
79
  return { interface: interfaceDef, implementation, deactivate } satisfies Capability<T>;
80
80
  };
81
81
 
82
- type LazyCapability<T, U> = () => Promise<{ default: (props: T) => MaybePromise<Capability<U>> }>;
82
+ type LoadCapability<T, U> = () => Promise<{ default: (props: T) => MaybePromise<Capability<U>> }>;
83
+ type LoadCapabilities<T> = () => Promise<{ default: (props: T) => MaybePromise<AnyCapability[]> }>;
84
+ // TODO(wittjosiah): Not having the array be `any` causes type errors when using the lazy capability.
85
+ type LazyCapability<T, U> = (props?: T) => Promise<() => Promise<Capability<U> | AnyCapability[]>>;
83
86
 
84
87
  /**
85
88
  * Helper to define a lazily loaded implementation of a capability.
86
89
  */
87
90
  export const lazy =
88
- <T, U>(c: LazyCapability<T, U>) =>
89
- (props?: T): Promise<Capability<U>> =>
90
- c().then(({ default: getCapability }) => {
91
- return getCapability(props as T);
92
- });
91
+ <T, U>(c: LoadCapability<T, U> | LoadCapabilities<T>): LazyCapability<T, U> =>
92
+ async (props?: T) => {
93
+ const { default: getCapability } = await c();
94
+ return async () => getCapability(props as T);
95
+ };
93
96
 
94
97
  /**
95
98
  * Context which is passed to plugins, allowing them to interact with each other.
@@ -136,7 +139,11 @@ export class PluginsContext {
136
139
  }
137
140
 
138
141
  current.push(new CapabilityImpl(moduleId, implementation));
139
- log('capability contributed', { id: interfaceDef.identifier, count: untracked(() => current.length) });
142
+ log('capability contributed', {
143
+ id: interfaceDef.identifier,
144
+ moduleId,
145
+ count: untracked(() => current.length),
146
+ });
140
147
  }
141
148
 
142
149
  /**
@@ -152,6 +159,8 @@ export class PluginsContext {
152
159
  if (index !== -1) {
153
160
  current.splice(index, 1);
154
161
  log('capability removed', { id: interfaceDef.identifier, count: untracked(() => current.length) });
162
+ } else {
163
+ log.warn('capability not removed', { id: interfaceDef.identifier });
155
164
  }
156
165
  }
157
166
 
@@ -62,10 +62,10 @@ describe('PluginManager', () => {
62
62
  const Test = definePlugin(testMeta, [Hello]);
63
63
 
64
64
  const manager = new PluginManager({ plugins: [Test], core: [], pluginLoader });
65
- manager.enable(testMeta.id);
65
+ await manager.enable(testMeta.id);
66
66
  expect(manager.enabled).toEqual([Test.meta.id]);
67
67
  expect(manager.modules).toEqual([Hello]);
68
- manager.disable(testMeta.id);
68
+ await manager.disable(testMeta.id);
69
69
  expect(manager.enabled).toEqual([]);
70
70
  expect(manager.modules).toEqual([]);
71
71
  });
@@ -111,7 +111,8 @@ describe('PluginManager', () => {
111
111
  const Fail = defineModule({
112
112
  id: 'dxos.org/test/fail',
113
113
  activatesOn: FailEvent,
114
- activate: async () => raise(new Error('test')),
114
+ // TODO(wittjosiah): Test and catch more failure modes.
115
+ activate: async () => async () => raise(new Error('test')),
115
116
  });
116
117
  plugins = [definePlugin(testMeta, [Hello, Fail])];
117
118
 
@@ -287,7 +288,7 @@ describe('PluginManager', () => {
287
288
  id: 'dxos.org/test/count',
288
289
  activatesOn: Events.Startup,
289
290
  activatesBefore: [CountEvent],
290
- activate: (context) => {
291
+ activate: async (context) => async () => {
291
292
  computeTotal(context);
292
293
  return contributes(Total, state);
293
294
  },
@@ -328,57 +329,18 @@ describe('PluginManager', () => {
328
329
 
329
330
  {
330
331
  await manager.disable(Test.meta.id);
331
- expect(manager.active).toEqual([...Test.modules.map((m) => m.id), Count.meta.id]);
332
- expect(manager.pendingReset).toEqual([CountEvent.id, Events.Startup.id]);
333
-
334
- const totals = manager.context.requestCapabilities(Total);
335
- expect(totals).toHaveLength(1);
336
- expect(totals[0].total).toEqual(6);
337
- }
338
-
339
- {
340
- await manager.reset(CountEvent);
341
- expect(manager.active).toEqual([Count.meta.id]);
342
- expect(manager.pendingReset).toEqual([Events.Startup.id]);
343
-
344
- const totals = manager.context.requestCapabilities(Total);
345
- expect(totals).toHaveLength(1);
346
- expect(totals[0].total).toEqual(6);
347
- }
348
-
349
- {
350
- await manager.reset(Events.Startup);
351
332
  expect(manager.active).toEqual([Count.meta.id]);
352
333
  expect(manager.pendingReset).toEqual([]);
353
334
 
354
335
  const totals = manager.context.requestCapabilities(Total);
355
336
  expect(totals).toHaveLength(1);
356
- expect(totals[0].total).toEqual(0);
337
+ // Total doesn't change because it is not reactive.
338
+ expect(totals[0].total).toEqual(6);
357
339
  }
358
340
 
359
341
  {
360
342
  await manager.enable(Test.meta.id);
361
- expect(manager.active).toEqual([Count.meta.id]);
362
- expect(manager.pendingReset).toEqual([CountEvent.id, Events.Startup.id]);
363
-
364
- const totals = manager.context.requestCapabilities(Total);
365
- expect(totals).toHaveLength(1);
366
- expect(totals[0].total).toEqual(0);
367
- }
368
-
369
- {
370
- await manager.reset(CountEvent);
371
343
  expect(manager.active).toEqual([Count.meta.id, ...Test.modules.map((m) => m.id)]);
372
- expect(manager.pendingReset).toEqual([Events.Startup.id]);
373
-
374
- const totals = manager.context.requestCapabilities(Total);
375
- expect(totals).toHaveLength(1);
376
- expect(totals[0].total).toEqual(0);
377
- }
378
-
379
- {
380
- await manager.reset(Events.Startup);
381
- expect(manager.active).toEqual([...Test.modules.map((m) => m.id), Count.meta.id]);
382
344
  expect(manager.pendingReset).toEqual([]);
383
345
 
384
346
  const totals = manager.context.requestCapabilities(Total);
@@ -464,9 +426,6 @@ describe('PluginManager', () => {
464
426
  using activeUpdates = updateCounter(() => {
465
427
  const _ = manager.active.length;
466
428
  });
467
- using pendingRemovalUpdates = updateCounter(() => {
468
- const _ = manager.pendingRemoval.length;
469
- });
470
429
  using eventsFiredUpdates = updateCounter(() => {
471
430
  const _ = manager.eventsFired.length;
472
431
  });
@@ -477,7 +436,6 @@ describe('PluginManager', () => {
477
436
  expect(enabledUpdates.count).toEqual(0);
478
437
  expect(modulesUpdates.count).toEqual(0);
479
438
  expect(activeUpdates.count).toEqual(0);
480
- expect(pendingRemovalUpdates.count).toEqual(0);
481
439
  expect(eventsFiredUpdates.count).toEqual(0);
482
440
  expect(pendingResetUpdates.count).toEqual(0);
483
441
 
@@ -486,7 +444,6 @@ describe('PluginManager', () => {
486
444
  expect(enabledUpdates.count).toEqual(1);
487
445
  expect(modulesUpdates.count).toEqual(1);
488
446
  expect(activeUpdates.count).toEqual(0);
489
- expect(pendingRemovalUpdates.count).toEqual(0);
490
447
  expect(eventsFiredUpdates.count).toEqual(0);
491
448
  expect(pendingResetUpdates.count).toEqual(0);
492
449
 
@@ -495,7 +452,6 @@ describe('PluginManager', () => {
495
452
  expect(enabledUpdates.count).toEqual(1);
496
453
  expect(modulesUpdates.count).toEqual(1);
497
454
  expect(activeUpdates.count).toEqual(1);
498
- expect(pendingRemovalUpdates.count).toEqual(0);
499
455
  expect(eventsFiredUpdates.count).toEqual(1);
500
456
  expect(pendingResetUpdates.count).toEqual(0);
501
457
 
@@ -503,17 +459,15 @@ describe('PluginManager', () => {
503
459
  expect(pluginUpdates.count).toEqual(2);
504
460
  expect(enabledUpdates.count).toEqual(2);
505
461
  expect(modulesUpdates.count).toEqual(2);
506
- expect(activeUpdates.count).toEqual(1);
507
- expect(pendingRemovalUpdates.count).toEqual(0);
462
+ expect(activeUpdates.count).toEqual(2);
508
463
  expect(eventsFiredUpdates.count).toEqual(1);
509
- expect(pendingResetUpdates.count).toEqual(1);
464
+ expect(pendingResetUpdates.count).toEqual(2);
510
465
 
511
466
  await manager.activate(CountEvent);
512
467
  expect(pluginUpdates.count).toEqual(2);
513
468
  expect(enabledUpdates.count).toEqual(2);
514
469
  expect(modulesUpdates.count).toEqual(2);
515
470
  expect(activeUpdates.count).toEqual(2);
516
- expect(pendingRemovalUpdates.count).toEqual(0);
517
471
  expect(eventsFiredUpdates.count).toEqual(1);
518
472
  expect(pendingResetUpdates.count).toEqual(2);
519
473
 
@@ -521,47 +475,42 @@ describe('PluginManager', () => {
521
475
  expect(pluginUpdates.count).toEqual(3);
522
476
  expect(enabledUpdates.count).toEqual(3);
523
477
  expect(modulesUpdates.count).toEqual(3);
524
- expect(activeUpdates.count).toEqual(2);
525
- expect(pendingRemovalUpdates.count).toEqual(0);
478
+ expect(activeUpdates.count).toEqual(3);
526
479
  expect(eventsFiredUpdates.count).toEqual(1);
527
- expect(pendingResetUpdates.count).toEqual(3);
480
+ expect(pendingResetUpdates.count).toEqual(4);
528
481
 
529
482
  await manager.reset(CountEvent);
530
483
  expect(pluginUpdates.count).toEqual(3);
531
484
  expect(enabledUpdates.count).toEqual(3);
532
485
  expect(modulesUpdates.count).toEqual(3);
533
- // Starts at 2, plus deactivates 2, plus activates 3.
534
- expect(activeUpdates.count).toEqual(7);
535
- expect(pendingRemovalUpdates.count).toEqual(0);
486
+ // Starts at 3, plus deactivates 3, plus activates 3.
487
+ expect(activeUpdates.count).toEqual(9);
536
488
  expect(eventsFiredUpdates.count).toEqual(1);
537
489
  expect(pendingResetUpdates.count).toEqual(4);
538
490
 
539
491
  await manager.disable(One.meta.id);
540
492
  expect(pluginUpdates.count).toEqual(3);
541
493
  expect(enabledUpdates.count).toEqual(4);
542
- expect(modulesUpdates.count).toEqual(3);
543
- expect(activeUpdates.count).toEqual(7);
544
- expect(pendingRemovalUpdates.count).toEqual(1);
494
+ expect(modulesUpdates.count).toEqual(4);
495
+ expect(activeUpdates.count).toEqual(10);
545
496
  expect(eventsFiredUpdates.count).toEqual(1);
546
- expect(pendingResetUpdates.count).toEqual(5);
497
+ expect(pendingResetUpdates.count).toEqual(4);
547
498
 
548
499
  await manager.remove(One.meta.id);
549
500
  expect(pluginUpdates.count).toEqual(4);
550
501
  expect(enabledUpdates.count).toEqual(4);
551
- expect(modulesUpdates.count).toEqual(3);
552
- expect(activeUpdates.count).toEqual(7);
553
- expect(pendingRemovalUpdates.count).toEqual(1);
502
+ expect(modulesUpdates.count).toEqual(4);
503
+ expect(activeUpdates.count).toEqual(10);
554
504
  expect(eventsFiredUpdates.count).toEqual(1);
555
- expect(pendingResetUpdates.count).toEqual(5);
505
+ expect(pendingResetUpdates.count).toEqual(4);
556
506
 
557
507
  await manager.reset(CountEvent);
558
508
  expect(pluginUpdates.count).toEqual(4);
559
509
  expect(enabledUpdates.count).toEqual(4);
560
510
  expect(modulesUpdates.count).toEqual(4);
561
- // Starts at 7, plus deactivates 3, plus activates 2.
562
- expect(activeUpdates.count).toEqual(12);
563
- expect(pendingRemovalUpdates.count).toEqual(2);
511
+ // Starts at 10, plus deactivates 2, plus activates 2.
512
+ expect(activeUpdates.count).toEqual(14);
564
513
  expect(eventsFiredUpdates.count).toEqual(1);
565
- expect(pendingResetUpdates.count).toEqual(6);
514
+ expect(pendingResetUpdates.count).toEqual(4);
566
515
  });
567
516
  });
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { untracked } from '@preact/signals-core';
6
- import { Effect, Either, Match } from 'effect';
6
+ import { Array as A, Effect, Either, Match, pipe } from 'effect';
7
7
 
8
8
  import { Event } from '@dxos/async';
9
9
  import { create, type ReactiveObject } from '@dxos/live-object';
@@ -35,7 +35,6 @@ type PluginManagerState = {
35
35
  // Modules
36
36
  modules: PluginModule[];
37
37
  active: string[];
38
- pendingRemoval: string[];
39
38
 
40
39
  // Events
41
40
  eventsFired: string[];
@@ -67,7 +66,6 @@ export class PluginManager {
67
66
  enabled,
68
67
  modules: [],
69
68
  active: [],
70
- pendingRemoval: [],
71
69
  pendingReset: [],
72
70
  eventsFired: [],
73
71
  });
@@ -121,15 +119,6 @@ export class PluginManager {
121
119
  return this._state.active;
122
120
  }
123
121
 
124
- /**
125
- * Ids of modules which are pending removal.
126
- *
127
- * @reactive
128
- */
129
- get pendingRemoval(): ReactiveObject<readonly string[]> {
130
- return this._state.pendingRemoval;
131
- }
132
-
133
122
  /**
134
123
  * Ids of events which have been fired.
135
124
  *
@@ -165,8 +154,8 @@ export class PluginManager {
165
154
  * Enables a plugin.
166
155
  * @param id The id of the plugin.
167
156
  */
168
- enable(id: string): boolean {
169
- return untracked(() => {
157
+ enable(id: string): Promise<boolean> {
158
+ return untracked(async () => {
170
159
  log('enable plugin', { id });
171
160
  const plugin = this._getPlugin(id);
172
161
  if (!plugin) {
@@ -181,6 +170,15 @@ export class PluginManager {
181
170
  this._addModule(module);
182
171
  this._setPendingResetByModule(module);
183
172
  });
173
+
174
+ log('pending reset', { events: [...this.pendingReset] });
175
+ await Effect.runPromise(
176
+ Effect.all(
177
+ this.pendingReset.map((event) => this._activate(event)),
178
+ { concurrency: 'unbounded' },
179
+ ),
180
+ );
181
+
184
182
  return true;
185
183
  });
186
184
  }
@@ -206,8 +204,8 @@ export class PluginManager {
206
204
  * Disables a plugin.
207
205
  * @param id The id of the plugin.
208
206
  */
209
- disable(id: string): boolean {
210
- return untracked(() => {
207
+ disable(id: string): Promise<boolean> {
208
+ return untracked(async () => {
211
209
  log('disable plugin', { id });
212
210
  if (this._state.core.includes(id)) {
213
211
  return false;
@@ -221,16 +219,10 @@ export class PluginManager {
221
219
  const enabledIndex = this._state.enabled.findIndex((enabled) => enabled === id);
222
220
  if (enabledIndex !== -1) {
223
221
  this._state.enabled.splice(enabledIndex, 1);
222
+ await Effect.runPromise(this._deactivate(id));
224
223
 
225
224
  plugin.modules.forEach((module) => {
226
- if (this._state.active.includes(module.id)) {
227
- this._setPendingResetByModule(module);
228
- if (!this._state.pendingRemoval.includes(module.id)) {
229
- this._state.pendingRemoval.push(module.id);
230
- }
231
- } else {
232
- this._removeModule(module.id);
233
- }
225
+ this._removeModule(module.id);
234
226
  });
235
227
  }
236
228
 
@@ -328,14 +320,8 @@ export class PluginManager {
328
320
  const activationEvents = getEvents(module.activatesOn)
329
321
  .map(eventKey)
330
322
  .filter((key) => this._state.eventsFired.includes(key));
331
- const parentEvents = activationEvents.flatMap((event) => {
332
- const modules = this._getActiveModules().filter((module) =>
333
- module.activatesBefore?.map(eventKey).includes(event),
334
- );
335
- return modules.flatMap((module) => getEvents(module.activatesOn)).map(eventKey);
336
- });
337
323
 
338
- const pendingReset = Array.from(new Set([...activationEvents, ...parentEvents])).filter(
324
+ const pendingReset = Array.from(new Set(activationEvents)).filter(
339
325
  (event) => !this._state.pendingReset.includes(event),
340
326
  );
341
327
  if (pendingReset.length > 0) {
@@ -345,64 +331,91 @@ export class PluginManager {
345
331
  });
346
332
  }
347
333
 
334
+ /**
335
+ * @internal
336
+ */
348
337
  // TODO(wittjosiah): Improve error typing.
349
- private _activate(event: ActivationEvent | string): Effect.Effect<boolean, Error> {
350
- const self = this;
351
- return Effect.gen(function* () {
338
+ _activate(event: ActivationEvent | string): Effect.Effect<boolean, Error> {
339
+ return Effect.gen(this, function* () {
352
340
  const key = typeof event === 'string' ? event : eventKey(event);
353
341
  log('activating', { key });
354
- const pendingIndex = self._state.pendingReset.findIndex((event) => event === key);
342
+ const pendingIndex = this._state.pendingReset.findIndex((event) => event === key);
355
343
  if (pendingIndex !== -1) {
356
- self._state.pendingReset.splice(pendingIndex, 1);
344
+ this._state.pendingReset.splice(pendingIndex, 1);
357
345
  }
358
346
 
359
- const modules = self._getInactiveModulesByEvent(key);
347
+ const modules = this._getInactiveModulesByEvent(key).filter((module) => {
348
+ const allOf = isAllOf(module.activatesOn);
349
+ if (!allOf) {
350
+ return true;
351
+ }
352
+
353
+ const events = module.activatesOn.events.filter((event) => eventKey(event) !== key);
354
+ return events.every((event) => this._state.eventsFired.includes(eventKey(event)));
355
+ });
360
356
  if (modules.length === 0) {
361
357
  log('no modules to activate', { key });
358
+ if (!this._state.eventsFired.includes(key)) {
359
+ this._state.eventsFired.push(key);
360
+ }
362
361
  return false;
363
362
  }
364
363
 
365
- self.activation.emit({ event: key, state: 'activating' });
366
-
367
- for (const module of modules) {
368
- if (
369
- isAllOf(module.activatesOn) &&
370
- !module.activatesOn.events
371
- .filter((event) => eventKey(event) !== key)
372
- .every((event) => self._state.eventsFired.includes(eventKey(event)))
373
- ) {
374
- continue;
375
- }
364
+ log('activating modules', { key, modules: modules.map((module) => module.id) });
365
+ this.activation.emit({ event: key, state: 'activating' });
376
366
 
377
- yield* Effect.all(module.activatesBefore?.map((event) => self._activate(event)) ?? []);
367
+ // Concurrently triggers loading of lazy capabilities.
368
+ const getCapabilities = yield* Effect.all(
369
+ modules.map(({ activate }) =>
370
+ Effect.tryPromise({
371
+ try: async () => activate(this.context),
372
+ catch: (error) => error as Error,
373
+ }),
374
+ ),
375
+ { concurrency: 'unbounded' },
376
+ );
378
377
 
379
- const result = yield* self._activateModule(module).pipe(Effect.either);
380
- if (Either.isLeft(result)) {
381
- self.activation.emit({ event: key, state: 'error', error: result.left });
382
- yield* Effect.fail(result.left);
383
- }
378
+ const result = yield* pipe(
379
+ modules,
380
+ A.zip(getCapabilities),
381
+ A.map(([module, getCapabilities]) => this._activateModule(module, getCapabilities)),
382
+ // TODO(wittjosiah): This currently can't be run in parallel.
383
+ // Running this with concurrency causes races with `allOf` activation events.
384
+ Effect.all,
385
+ Effect.either,
386
+ );
384
387
 
385
- yield* Effect.all(module.activatesAfter?.map((event) => self._activate(event)) ?? []);
388
+ if (Either.isLeft(result)) {
389
+ this.activation.emit({ event: key, state: 'error', error: result.left });
390
+ yield* Effect.fail(result.left);
386
391
  }
387
392
 
388
- if (!self._state.eventsFired.includes(key)) {
389
- self._state.eventsFired.push(key);
393
+ if (!this._state.eventsFired.includes(key)) {
394
+ this._state.eventsFired.push(key);
390
395
  }
391
396
 
392
- self.activation.emit({ event: key, state: 'activated' });
397
+ this.activation.emit({ event: key, state: 'activated' });
393
398
  log('activated', { key });
394
399
 
395
400
  return true;
396
401
  });
397
402
  }
398
403
 
399
- private _activateModule(module: PluginModule): Effect.Effect<void, Error> {
400
- const self = this;
401
- return Effect.gen(function* () {
404
+ private _activateModule(
405
+ module: PluginModule,
406
+ getCapabilities: AnyCapability | AnyCapability[] | (() => Promise<AnyCapability | AnyCapability[]>),
407
+ ): Effect.Effect<void, Error> {
408
+ return Effect.gen(this, function* () {
409
+ yield* Effect.all(module.activatesBefore?.map((event) => this._activate(event)) ?? [], {
410
+ concurrency: 'unbounded',
411
+ });
412
+
413
+ log('activating module...', { module: module.id });
402
414
  // TODO(wittjosiah): This is not handling errors thrown if this is synchronous.
403
- const program = module.activate(self.context);
404
- const maybeCapabilities = yield* Match.value(program).pipe(
405
- Match.when(Effect.isEffect, (effect) => effect),
415
+ const maybeCapabilities = typeof getCapabilities === 'function' ? getCapabilities() : getCapabilities;
416
+ const resolvedCapabilities = yield* Match.value(maybeCapabilities).pipe(
417
+ // TODO(wittjosiah): Activate with an effect?
418
+ // Match.when(Effect.isEffect, (effect) => effect),
406
419
  Match.when(isPromise, (promise) =>
407
420
  Effect.tryPromise({
408
421
  try: () => promise,
@@ -411,42 +424,48 @@ export class PluginManager {
411
424
  ),
412
425
  Match.orElse((program) => Effect.succeed(program)),
413
426
  );
414
- const capabilities = Match.value(maybeCapabilities).pipe(
427
+ const capabilities = Match.value(resolvedCapabilities).pipe(
415
428
  Match.when(Array.isArray, (array) => array),
416
429
  Match.orElse((value) => [value]),
417
430
  );
418
431
  capabilities.forEach((capability) => {
419
- self.context.contributeCapability({ module: module.id, ...capability });
432
+ this.context.contributeCapability({ module: module.id, ...capability });
433
+ });
434
+ this._state.active.push(module.id);
435
+ this._capabilities.set(module.id, capabilities);
436
+ log('activated module', { module: module.id });
437
+
438
+ yield* Effect.all(module.activatesAfter?.map((event) => this._activate(event)) ?? [], {
439
+ concurrency: 'unbounded',
420
440
  });
421
- self._state.active.push(module.id);
422
- self._capabilities.set(module.id, capabilities);
423
441
  });
424
442
  }
425
443
 
426
444
  private _deactivate(id: string): Effect.Effect<boolean, Error> {
427
- const self = this;
428
- return Effect.gen(function* () {
429
- const plugin = self._getPlugin(id);
445
+ return Effect.gen(this, function* () {
446
+ const plugin = this._getPlugin(id);
430
447
  if (!plugin) {
431
448
  return false;
432
449
  }
433
450
 
434
451
  const modules = plugin.modules;
435
- const results = yield* Effect.all(modules.map((module) => self._deactivateModule(module)));
452
+ const results = yield* Effect.all(
453
+ modules.map((module) => this._deactivateModule(module)),
454
+ { concurrency: 'unbounded' },
455
+ );
436
456
  return results.every((result) => result);
437
457
  });
438
458
  }
439
459
 
440
460
  private _deactivateModule(module: PluginModule): Effect.Effect<boolean, Error> {
441
- const self = this;
442
- return Effect.gen(function* () {
461
+ return Effect.gen(this, function* () {
443
462
  const id = module.id;
444
463
  log('deactivating', { id });
445
464
 
446
- const capabilities = self._capabilities.get(id);
465
+ const capabilities = this._capabilities.get(id);
447
466
  if (capabilities) {
448
467
  for (const capability of capabilities) {
449
- self.context.removeCapability(capability.interface, capability.implementation);
468
+ this.context.removeCapability(capability.interface, capability.implementation);
450
469
  const program = capability.deactivate?.();
451
470
  yield* Match.value(program).pipe(
452
471
  Match.when(Effect.isEffect, (effect) => effect),
@@ -459,12 +478,12 @@ export class PluginManager {
459
478
  Match.orElse((program) => Effect.succeed(program)),
460
479
  );
461
480
  }
462
- self._capabilities.delete(id);
481
+ this._capabilities.delete(id);
463
482
  }
464
483
 
465
- const activeIndex = self._state.active.findIndex((event) => event === id);
484
+ const activeIndex = this._state.active.findIndex((event) => event === id);
466
485
  if (activeIndex !== -1) {
467
- self._state.active.splice(activeIndex, 1);
486
+ this._state.active.splice(activeIndex, 1);
468
487
  }
469
488
 
470
489
  log('deactivated', { id });
@@ -473,22 +492,17 @@ export class PluginManager {
473
492
  }
474
493
 
475
494
  private _reset(event: ActivationEvent | string): Effect.Effect<boolean, Error> {
476
- const self = this;
477
- return Effect.gen(function* () {
495
+ return Effect.gen(this, function* () {
478
496
  const key = typeof event === 'string' ? event : eventKey(event);
479
497
  log('reset', { key });
480
- const modules = self._getActiveModulesByEvent(key);
481
- const results = yield* Effect.all(modules.map((module) => self._deactivateModule(module)));
482
-
483
- if (self._state.pendingRemoval.length > 0) {
484
- self._state.pendingRemoval.forEach((id) => {
485
- self._removeModule(id);
486
- });
487
- self._state.pendingRemoval.splice(0, self._state.pendingRemoval.length);
488
- }
498
+ const modules = this._getActiveModulesByEvent(key);
499
+ const results = yield* Effect.all(
500
+ modules.map((module) => this._deactivateModule(module)),
501
+ { concurrency: 'unbounded' },
502
+ );
489
503
 
490
504
  if (results.every((result) => result)) {
491
- return yield* self._activate(key);
505
+ return yield* this._activate(key);
492
506
  } else {
493
507
  return false;
494
508
  }
@@ -2,8 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Effect } from 'effect';
6
-
7
5
  import { type MaybePromise } from '@dxos/util';
8
6
 
9
7
  import { type AnyCapability, type PluginsContext } from './capabilities';
@@ -39,7 +37,7 @@ interface PluginModuleInterface {
39
37
  */
40
38
  activate: (
41
39
  context: PluginsContext,
42
- ) => MaybePromise<AnyCapability | AnyCapability[]> | Effect.Effect<AnyCapability | AnyCapability[], Error>;
40
+ ) => MaybePromise<AnyCapability | AnyCapability[]> | Promise<() => Promise<AnyCapability | AnyCapability[]>>;
43
41
  }
44
42
 
45
43
  /**
@@ -98,6 +96,11 @@ export type PluginMeta = {
98
96
  */
99
97
  source?: string;
100
98
 
99
+ /**
100
+ * URL of screenshot.
101
+ */
102
+ screenshots?: string[];
103
+
101
104
  /**
102
105
  * Tags to help categorize the plugin.
103
106
  */
@@ -12,6 +12,6 @@ export const DebugPlugin = () =>
12
12
  defineModule({
13
13
  id: 'dxos.org/test/debug/main',
14
14
  activatesOn: Events.Startup,
15
- activate: () => Debug(),
15
+ activate: Debug,
16
16
  }),
17
17
  ]);
@@ -6,34 +6,34 @@ import React, { useCallback } from 'react';
6
6
 
7
7
  import { Button } from '@dxos/react-ui';
8
8
 
9
- import { CountEvent, createPluginId, Number } from './generator';
9
+ import { createGeneratorIntent, createPluginId, Number } from './generator';
10
10
  import { Capabilities } from '../../common';
11
11
  import { contributes } from '../../core';
12
- import { usePluginManager } from '../../react';
12
+ import { createIntent } from '../../plugin-intent';
13
+ import { useCapabilities, useIntentDispatcher, usePluginManager } from '../../react';
13
14
 
14
15
  export const Toolbar = () => {
15
16
  const manager = usePluginManager();
17
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
16
18
 
17
19
  const handleAdd = useCallback(async () => {
18
20
  const id = createPluginId(Math.random().toString(16).substring(2, 8));
19
21
  await manager.add(id);
20
22
  }, [manager]);
21
23
 
22
- const handleCount = useCallback(async () => {
23
- if (manager.pendingReset.includes(CountEvent.id)) {
24
- await manager.reset(CountEvent);
25
- } else {
26
- await manager.activate(CountEvent);
27
- }
28
- }, [manager]);
24
+ const count = useCapabilities(Number).reduce((acc, curr) => acc + curr, 0);
29
25
 
30
- const count = manager.context.requestCapabilities(Number).reduce((acc, curr) => acc + curr, 0);
26
+ const generatorPlugins = manager.plugins.filter((plugin) => plugin.meta.id.startsWith('dxos.org/test/generator/'));
31
27
 
32
28
  return (
33
29
  <>
34
30
  <Button onClick={handleAdd}>Add</Button>
35
- <Button onClick={handleCount}>Count</Button>
36
31
  <div className='flex items-center'>Count: {count}</div>
32
+ {generatorPlugins.map((plugin) => (
33
+ <Button key={plugin.meta.id} onClick={() => dispatch(createIntent(createGeneratorIntent(plugin.meta.id)))}>
34
+ {plugin.meta.id.replace('dxos.org/test/generator/', '')}
35
+ </Button>
36
+ ))}
37
37
  </>
38
38
  );
39
39
  };