@dxos/app-framework 0.11.1 → 1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
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.
- package/API.md +275 -0
- package/ARCHITECTURE.md +190 -0
- package/CHANGELOG.md +73 -0
- package/README.md +3 -0
- package/dist/lib/chunk-SurfaceManager.mjs +56 -7
- package/dist/lib/chunk-SurfaceManager.mjs.map +1 -1
- package/dist/lib/chunk-capability.mjs +372 -10
- package/dist/lib/chunk-capability.mjs.map +1 -1
- package/dist/lib/chunk-capability2.mjs +15 -0
- package/dist/lib/chunk-capability2.mjs.map +1 -0
- package/dist/lib/chunk-devtools.mjs +108 -0
- package/dist/lib/chunk-devtools.mjs.map +1 -0
- package/dist/lib/chunk-history-tracker.mjs +2 -2
- package/dist/lib/chunk-history-tracker.mjs.map +1 -1
- package/dist/lib/chunk-plugin-manager.mjs +2294 -953
- package/dist/lib/chunk-plugin-manager.mjs.map +1 -1
- package/dist/lib/chunk-plugin-manifest.mjs +1 -1
- package/dist/lib/chunk-process-manager-capability.mjs +42 -18
- package/dist/lib/chunk-process-manager-capability.mjs.map +1 -1
- package/dist/lib/chunk-url-loader.mjs +1 -1
- package/dist/lib/cli.mjs +4 -3
- package/dist/lib/cli.mjs.map +1 -1
- package/dist/lib/common/activation-events.mjs +32 -19
- package/dist/lib/common/activation-events.mjs.map +1 -1
- package/dist/lib/common/capabilities.mjs +21 -27
- package/dist/lib/common/capabilities.mjs.map +1 -1
- package/dist/lib/core/activation-event.mjs +33 -2
- package/dist/lib/core/activation-event.mjs.map +1 -1
- package/dist/lib/core/capability.mjs +2 -189
- package/dist/lib/core/plugin-manager.mjs +14 -2
- package/dist/lib/core/plugin-manager.mjs.map +1 -0
- package/dist/lib/core/plugin.mjs +59 -57
- package/dist/lib/core/plugin.mjs.map +1 -1
- package/dist/lib/index.mjs +52 -40
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/testing.mjs +87 -23
- package/dist/lib/testing.mjs.map +1 -1
- package/dist/lib/ui.mjs +67 -44
- package/dist/lib/ui.mjs.map +1 -1
- package/dist/lib/vite-plugin.mjs +31 -22
- package/dist/lib/vite-plugin.mjs.map +1 -1
- package/dist/plugin/node-esm/index.mjs +88 -55
- package/dist/plugin/node-esm/index.mjs.map +4 -4
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/types/src/common/activation-events.d.ts +25 -12
- package/dist/types/src/common/activation-events.d.ts.map +1 -1
- package/dist/types/src/common/capabilities.d.ts +27 -27
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/core/activation-event.d.ts +28 -0
- package/dist/types/src/core/activation-event.d.ts.map +1 -1
- package/dist/types/src/core/capability-manager.d.ts +27 -1
- package/dist/types/src/core/capability-manager.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +277 -39
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/capability.test.d.ts +2 -0
- package/dist/types/src/core/capability.test.d.ts.map +1 -0
- package/dist/types/src/core/errors.d.ts +230 -0
- package/dist/types/src/core/errors.d.ts.map +1 -0
- package/dist/types/src/core/index.d.ts +1 -0
- package/dist/types/src/core/index.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager/activation-graph.d.ts +82 -0
- package/dist/types/src/core/plugin-manager/activation-graph.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/activation-graph.test.d.ts +2 -0
- package/dist/types/src/core/plugin-manager/activation-graph.test.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/activation-scheduler.d.ts +98 -0
- package/dist/types/src/core/plugin-manager/activation-scheduler.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/idle.d.ts +11 -0
- package/dist/types/src/core/plugin-manager/idle.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/index.d.ts +2 -0
- package/dist/types/src/core/plugin-manager/index.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/manager-state.d.ts +129 -0
- package/dist/types/src/core/plugin-manager/manager-state.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/manager-types.d.ts +104 -0
- package/dist/types/src/core/plugin-manager/manager-types.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/module-loader.d.ts +74 -0
- package/dist/types/src/core/plugin-manager/module-loader.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/plugin-catalog.d.ts +74 -0
- package/dist/types/src/core/plugin-manager/plugin-catalog.d.ts.map +1 -0
- package/dist/types/src/core/{plugin-manager.d.ts → plugin-manager/plugin-manager.d.ts} +15 -102
- package/dist/types/src/core/plugin-manager/plugin-manager.d.ts.map +1 -0
- package/dist/types/src/core/plugin-manager/plugin-manager.test.d.ts.map +1 -0
- package/dist/types/src/core/plugin.d.ts +126 -51
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/plugin.test.d.ts +2 -0
- package/dist/types/src/core/plugin.test.d.ts.map +1 -0
- package/dist/types/src/devtools.d.ts +60 -0
- package/dist/types/src/devtools.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/ProcessManagerPlugin.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/history/capability.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/history/types.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/history/types.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/history/undo-mapping.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/history/undo-mapping.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/history/undo-registry.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/history/undo-registry.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.test.d.ts +2 -0
- package/dist/types/src/plugin-process-manager/process-manager-capability.test.d.ts.map +1 -0
- package/dist/types/src/plugin-process-manager/testing.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/testing.d.ts.map +1 -1
- package/dist/types/src/testing/demand-gated.d.ts +26 -0
- package/dist/types/src/testing/demand-gated.d.ts.map +1 -0
- package/dist/types/src/testing/harness.d.ts +14 -3
- package/dist/types/src/testing/harness.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/operationCapture.d.ts +2 -2
- package/dist/types/src/testing/operationCapture.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts +8 -3
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts +1 -1
- package/dist/types/src/testing/withSurfaceDebug.d.ts +1 -1
- package/dist/types/src/testing/withSurfaceDebug.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.stories.d.ts +1 -1
- package/dist/types/src/ui/components/HomeSection/HomeSection.stories.d.ts +1 -1
- package/dist/types/src/ui/components/PluginManager/PluginManagerContext.stories.d.ts +1 -1
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts +2 -3
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceManager.d.ts +13 -0
- package/dist/types/src/ui/components/Surface/SurfaceManager.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/index.d.ts +1 -0
- package/dist/types/src/ui/hooks/index.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useActivationSignal.d.ts +9 -0
- package/dist/types/src/ui/hooks/useActivationSignal.d.ts.map +1 -0
- package/dist/types/src/ui/hooks/useApp.d.ts +0 -1
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts +19 -5
- package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useProcessManagerRuntime.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/load.d.ts +7 -5
- package/dist/types/src/vite-plugin/load.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/docs/activation-sequence.md +138 -0
- package/moon.yml +1 -0
- package/package.json +30 -33
- package/src/cli/cli.ts +6 -6
- package/src/common/activation-events.ts +27 -21
- package/src/common/capabilities.ts +39 -33
- package/src/core/activation-event.ts +31 -0
- package/src/core/capability-manager.test.ts +50 -12
- package/src/core/capability-manager.ts +107 -6
- package/src/core/capability.test.ts +157 -0
- package/src/core/capability.ts +488 -80
- package/src/core/errors.ts +82 -0
- package/src/core/index.ts +1 -0
- package/src/core/plugin-manager/activation-graph.test.ts +177 -0
- package/src/core/plugin-manager/activation-graph.ts +334 -0
- package/src/core/plugin-manager/activation-scheduler.ts +972 -0
- package/src/core/plugin-manager/idle.ts +64 -0
- package/src/core/plugin-manager/index.ts +7 -0
- package/src/core/plugin-manager/manager-state.ts +366 -0
- package/src/core/plugin-manager/manager-types.ts +68 -0
- package/src/core/plugin-manager/module-loader.ts +552 -0
- package/src/core/plugin-manager/plugin-catalog.ts +661 -0
- package/src/core/{plugin-manager.test.ts → plugin-manager/plugin-manager.test.ts} +1407 -99
- package/src/core/plugin-manager/plugin-manager.ts +591 -0
- package/src/core/plugin.test.ts +349 -0
- package/src/core/plugin.ts +213 -78
- package/src/devtools.ts +209 -0
- package/src/globals.d.ts +16 -0
- package/src/index.ts +1 -0
- package/src/plugin-process-manager/ProcessManagerPlugin.ts +36 -13
- package/src/plugin-process-manager/history/capability.ts +6 -8
- package/src/plugin-process-manager/history/history-tracker.test.ts +1 -1
- package/src/plugin-process-manager/history/types.ts +1 -1
- package/src/plugin-process-manager/history/undo-mapping.ts +1 -1
- package/src/plugin-process-manager/history/undo-registry.ts +1 -1
- package/src/plugin-process-manager/process-manager-capability.test.ts +85 -0
- package/src/plugin-process-manager/process-manager-capability.ts +54 -25
- package/src/plugin-process-manager/testing.ts +1 -1
- package/src/testing/demand-gated.ts +54 -0
- package/src/testing/harness.ts +19 -4
- package/src/testing/index.ts +1 -0
- package/src/testing/operationCapture.ts +3 -3
- package/src/testing/react.test.tsx +5 -5
- package/src/testing/withPluginManager.stories.tsx +1 -1
- package/src/testing/withPluginManager.tsx +70 -21
- package/src/testing/withSurfaceDebug.tsx +1 -1
- package/src/ui/components/App/App.stories.tsx +3 -3
- package/src/ui/components/App/App.tsx +21 -8
- package/src/ui/components/PluginManager/PluginManagerContext.stories.tsx +5 -5
- package/src/ui/components/PluginManager/PluginManagerProvider.ts +2 -3
- package/src/ui/components/Surface/SurfaceComponent.test.tsx +66 -17
- package/src/ui/components/Surface/SurfaceComponent.tsx +47 -3
- package/src/ui/components/Surface/SurfaceManager.ts +27 -0
- package/src/ui/hooks/index.ts +1 -0
- package/src/ui/hooks/useActivationSignal.ts +26 -0
- package/src/ui/hooks/useApp.test.tsx +7 -4
- package/src/ui/hooks/useApp.tsx +2 -8
- package/src/ui/hooks/useCapabilities.ts +30 -17
- package/src/ui/hooks/useProcessManagerRuntime.ts +2 -1
- package/src/vite-plugin/boot-loader/loader-app/entry.tsx +6 -0
- package/src/vite-plugin/load.ts +30 -28
- package/src/vite-plugin/packages.ts +0 -1
- package/tsconfig.json +3 -12
- package/vite.config.ts +1 -1
- package/dist/lib/core/capability.mjs.map +0 -1
- package/dist/types/src/common/annotations.d.ts +0 -1
- package/dist/types/src/common/annotations.d.ts.map +0 -1
- package/dist/types/src/core/plugin-manager.d.ts.map +0 -1
- package/dist/types/src/core/plugin-manager.test.d.ts.map +0 -1
- package/src/common/annotations.ts +0 -3
- package/src/core/plugin-manager.ts +0 -1719
- package/src/vite-plugin/boot-loader/BootLoader.solid-stories.tsx +0 -198
- /package/dist/types/src/core/{plugin-manager.test.d.ts → plugin-manager/plugin-manager.test.d.ts} +0 -0
package/API.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# DXOS App Framework API
|
|
2
|
+
|
|
3
|
+
Companion to [ARCHITECTURE.md](./ARCHITECTURE.md), which covers how the pieces relate. This is the
|
|
4
|
+
API surface.
|
|
5
|
+
|
|
6
|
+
## Imports
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import {
|
|
10
|
+
ActivationEvent, // Runtime activation events (make/oneOf/allOf)
|
|
11
|
+
ActivationEvents, // Well-known framework events (Startup, ...)
|
|
12
|
+
Capabilities, // Well-known capability tags (ReactSurface, OperationHandler, ...)
|
|
13
|
+
Capability, // Capability tags, contributions, module authoring
|
|
14
|
+
Plugin, // Plugin definition + builder
|
|
15
|
+
PluginManager, // Manager type (usually obtained, not constructed)
|
|
16
|
+
} from '@dxos/app-framework';
|
|
17
|
+
import { Surface, useApp, useCapabilities, useCapability } from '@dxos/app-framework/ui';
|
|
18
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Defining a capability
|
|
24
|
+
|
|
25
|
+
A capability is a typed, NSID-keyed slot. **Arity is chosen at definition** and decides what
|
|
26
|
+
consumers get.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// Multi (the default): an open registry many modules contribute to.
|
|
30
|
+
export const Template = Capability.make<Template>()('org.dxos.plugin.projects.capability.template');
|
|
31
|
+
|
|
32
|
+
// Singleton: exactly one provider.
|
|
33
|
+
export const Client = Capability.makeSingleton<ClientApi>()('org.dxos.plugin.client.capability.client');
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Both are curried — `make<T>()(nsid)` — so the NSID string literal is captured and brands the
|
|
37
|
+
identifier. NSIDs must be camelCase (`DXN.Name`). The single-call form `make<T>(nsid)` still compiles
|
|
38
|
+
but widens the NSID to `string`, making the compile-time check vacuous; prefer the curried form.
|
|
39
|
+
|
|
40
|
+
| | `makeSingleton` → `Tag<T>` | `make` → `MultiTag<T>` |
|
|
41
|
+
| ------------------ | ------------------------------------ | ----------------------------------------- |
|
|
42
|
+
| `yield* tag` gives | `T` | `Contributions<T>` (live view) |
|
|
43
|
+
| Requiring it | gates activation on its one provider | never gates — the collection may be empty |
|
|
44
|
+
| Two providers | `DuplicateProviderError` | normal |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Authoring a module
|
|
49
|
+
|
|
50
|
+
A module is a body plus a declaration of what it `requires` and `provides`.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// Code-split (the common case) — the chunk loads when the module activates.
|
|
54
|
+
export const Coordinator = Capability.lazyModule(
|
|
55
|
+
'ConnectorCoordinator',
|
|
56
|
+
{ requires: [ClientCapabilities.Client], provides: [ConnectorCoordinator] },
|
|
57
|
+
() => import('./connector-coordinator'),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Eager — body defined inline, no separate chunk.
|
|
61
|
+
export const Extractor = Capability.inlineModule(
|
|
62
|
+
'SummarizeExtractor',
|
|
63
|
+
{ provides: [InboxCapabilities.ObjectExtractor] },
|
|
64
|
+
() => Effect.succeed(Capability.contribute(InboxCapabilities.ObjectExtractor, extractor)),
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The body is written with `Capability.makeModule` and yields its declared requirements:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
export default Capability.makeModule(
|
|
72
|
+
Effect.fnUntraced(function* () {
|
|
73
|
+
const client = yield* ClientCapabilities.Client; // declared in `requires`
|
|
74
|
+
const coordinator = new Coordinator(client);
|
|
75
|
+
return Capability.contribute(ConnectorCoordinator, coordinator);
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Yielding a tag that is not in `requires` is a type error (it escapes the `R` channel); failing to
|
|
81
|
+
contribute a declared `provides` is also a type error.
|
|
82
|
+
|
|
83
|
+
**Makers.** Capability owners export a maker so call sites don't repeat `provides`.
|
|
84
|
+
`@dxos/app-toolkit` ships them for the common capabilities:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
export const ReactSurface = AppCapability.surface(() => import('./react-surface'));
|
|
88
|
+
export const AppGraphBuilder = AppCapability.appGraphBuilder(() => import('./app-graph-builder'), {
|
|
89
|
+
requires: [SomeCapabilities.Thing],
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Build your own with `Capability.moduleMaker(name, tag)`.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Composing a plugin
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
export const FooPlugin = Plugin.define(meta).pipe(
|
|
101
|
+
Plugin.addModule(ReactSurface),
|
|
102
|
+
Plugin.addModule(Coordinator),
|
|
103
|
+
Plugin.addModule(AppCapability.translations(translations)),
|
|
104
|
+
Plugin.addModule(AppCapability.schema([Foo.Foo])),
|
|
105
|
+
Plugin.make,
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`Plugin.define(meta)` starts a builder; each `addModule` adds one module; `Plugin.make` seals it.
|
|
110
|
+
When the plugin takes options, `addModule` also accepts `(options) => module` and makers accept a
|
|
111
|
+
`props` mapping from plugin options to body props. If every option is optional, calling
|
|
112
|
+
`FooPlugin()` with no argument is allowed.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Consuming capabilities
|
|
117
|
+
|
|
118
|
+
Three access paths, for three situations.
|
|
119
|
+
|
|
120
|
+
**1. Inside a module body** — declared, type-checked, ordering-aware.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
Effect.fnUntraced(function* () {
|
|
124
|
+
const client = yield* ClientCapabilities.Client; // singleton → T
|
|
125
|
+
const templates = yield* ProjectCapabilities.Template; // multi → Contributions<T>
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**2. Outside a module** (operations, callbacks, non-module code) — dynamic, string-keyed.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
const handlers = yield* Capability.getAll(Capabilities.OperationHandler);
|
|
134
|
+
const maybe = yield* Capability.getOption(AppCapabilities.ProgressRegistry);
|
|
135
|
+
const surfaces = yield* Capability.atom(Capabilities.ReactSurface); // reactive
|
|
136
|
+
const client = yield* Capability.waitFor(ClientCapabilities.Client); // resolves when contributed
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**3. React.**
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
const surfaces = useCapabilities(Capabilities.ReactSurface);
|
|
144
|
+
const client = useCapability(ClientCapabilities.Client);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> In graph-extension atom callbacks always use `Capability.atom`, never a synchronous
|
|
148
|
+
> `Capability.get` — a sync read inside an atom body caches a defect fallback with no reactive
|
|
149
|
+
> dependency.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Contributing values
|
|
154
|
+
|
|
155
|
+
A module returns its contributions from `activate`. `contribute` carries one value, `contributeAll`
|
|
156
|
+
carries several for the same capability; both produce a single `Contribution`.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
return [
|
|
160
|
+
Capability.contribute(Capabilities.ReactSurface, surface),
|
|
161
|
+
Capability.contributeAll(Capabilities.LayerSpec, [clientSpec, databaseSpec]),
|
|
162
|
+
];
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
A module providing exactly one capability may return the `Contribution` directly, without the array.
|
|
166
|
+
An optional third argument is a `deactivate` hook, run once when the module is deactivated.
|
|
167
|
+
|
|
168
|
+
The return is checked against the module's declared `provides`: omitting a declared capability is a
|
|
169
|
+
type error, and contributing an undeclared one fails at runtime.
|
|
170
|
+
|
|
171
|
+
## Reading a multi capability
|
|
172
|
+
|
|
173
|
+
A multi capability has no single value, and its membership changes as plugins are enabled and
|
|
174
|
+
disabled. Reading one therefore gives a live `Contributions<T>` view rather than an array:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
Effect.fnUntraced(function* () {
|
|
178
|
+
const templates = yield* ProjectCapabilities.Template; // Contributions<Template>
|
|
179
|
+
|
|
180
|
+
templates.get(); // readonly Template[] — snapshot as of now
|
|
181
|
+
templates.subscribe((values) => setTemplates(values)); // → unsubscribe
|
|
182
|
+
templates.atom; // Atom<Template[]> for Atom-based composition
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`get()` is a point-in-time snapshot: hold onto it and it will not reflect capabilities contributed
|
|
187
|
+
later. Consume the collection reactively — via `subscribe`, `atom`, or the React hooks — unless the
|
|
188
|
+
value is used immediately and discarded.
|
|
189
|
+
|
|
190
|
+
### `Contribution` vs `Contributions`
|
|
191
|
+
|
|
192
|
+
Two types one letter apart, on either side of the registry:
|
|
193
|
+
|
|
194
|
+
| | `Contribution` | `Contributions<T>` |
|
|
195
|
+
| ----------- | ----------------------------------------- | ------------------------------------------------------------ |
|
|
196
|
+
| Direction | what a module **produces** | what a consumer **reads** |
|
|
197
|
+
| Produced by | `Capability.contribute` / `contributeAll` | `yield*` on a `MultiTag`, or `Capability.contributions(tag)` |
|
|
198
|
+
| Shape | `{ capability, values, deactivate? }` | `{ atom, get(), subscribe() }` |
|
|
199
|
+
| Scope | one module's values for one capability | every module's values for one capability |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Activation
|
|
204
|
+
|
|
205
|
+
A module activates in one of two modes, normalized onto `PluginModule.activation`:
|
|
206
|
+
|
|
207
|
+
- **Dependency mode** (the default) — declared `requires`/`provides`. The manager topologically
|
|
208
|
+
orders modules so providers run before consumers. A module with no `requires` is a startup root.
|
|
209
|
+
- **Event mode** — `activatesOn` a genuine runtime event; `requires` are pulled on demand when it
|
|
210
|
+
fires.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
Capability.lazyModule(
|
|
214
|
+
'OnSpaceCreated',
|
|
215
|
+
{ activatesOn: SpaceEvents.SpaceCreated, provides: [] },
|
|
216
|
+
() => import('./on-space-created'),
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Surviving events are runtime occurrences only — `ActivationEvents.Startup`, `SpacesReady`,
|
|
221
|
+
`SpaceCreated`, `IdentityCreated`, `TypeAdded`, plus parameterized `createStateEvent` /
|
|
222
|
+
`createSettingsEvent`. Ordering-only `Setup*`/`*Ready` events were removed; express ordering as
|
|
223
|
+
`requires`/`provides`.
|
|
224
|
+
|
|
225
|
+
Structural problems isolate rather than abort startup: a dependency cycle, duplicate singleton
|
|
226
|
+
provider, or unsatisfiable requirement puts the owning plugin in an error state
|
|
227
|
+
(`DependencyCycleError`, `DuplicateProviderError`, `MissingProviderError`,
|
|
228
|
+
`ProvidesMismatchError`) and everything else proceeds.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Well-known capabilities
|
|
233
|
+
|
|
234
|
+
`Capabilities` (framework) — `ReactSurface`, `ReactContext`, `ReactRoot`, `OperationHandler`,
|
|
235
|
+
`OperationInvoker`, `UndoMapping`, `Command`, `Layer`, `LayerSpec`, `TraceSink`, `AtomRegistry`,
|
|
236
|
+
`ServiceResolver`, `ProcessManagerRuntime`, `ProcessMonitor`, `PluginManager`.
|
|
237
|
+
|
|
238
|
+
`AppCapabilities` (`@dxos/app-toolkit`) — `Translations`, `Schema`, `Settings`, `AppGraph`,
|
|
239
|
+
`AppGraphBuilder`, `SkillDefinition`, `PluginAsset`, `Toolkit`, `NavigationTargetResolver`,
|
|
240
|
+
`NavigationHandler`, `TextContent`, `CommentConfig`, `AnchorSort`, `AnchorResolver`, `Layout`.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Testing
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
withPluginManager({
|
|
248
|
+
plugins: [ClientPlugin(), FooPlugin()],
|
|
249
|
+
capabilities: [Capability.contribute(SomeCapability, stub)], // takes Contribution[]
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`@dxos/app-framework/testing` also exports `harness` (headless activation assertions) and
|
|
254
|
+
`createComposerTestApp`.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Type reference
|
|
259
|
+
|
|
260
|
+
| Type | Meaning |
|
|
261
|
+
| ----------------------------- | -------------------------------------------------------------- |
|
|
262
|
+
| `Capability.Tag<T, S>` | Singleton tag; `yield*` → `T` |
|
|
263
|
+
| `Capability.MultiTag<T, S>` | Multi tag; `yield*` → `Contributions<T>` |
|
|
264
|
+
| `Capability.AnyTag` | `Tag \| MultiTag` |
|
|
265
|
+
| `Capability.Contribution` | One branded contribution (capability + n values) |
|
|
266
|
+
| `Capability.Contributions<T>` | Live view: `{ atom, get(), subscribe() }` |
|
|
267
|
+
| `Capability.Any` | A registry entry: `{ interface, implementation, deactivate? }` |
|
|
268
|
+
| `Capability.Module<Options>` | An authored module, opaque except for its options type |
|
|
269
|
+
| `Plugin.Plugin` | `{ meta, modules }` |
|
|
270
|
+
| `Plugin.PluginModule` | `{ id, activation, activate() }` |
|
|
271
|
+
| `Plugin.ActivationSpec` | `dependency \| event` discriminated union |
|
|
272
|
+
|
|
273
|
+
`Contribution` → `Capability.Any` is a 1-to-n expansion performed by the manager
|
|
274
|
+
(`CapabilityManager.expandContributions`); see
|
|
275
|
+
[ARCHITECTURE.md](./ARCHITECTURE.md#the-two-layer-split).
|
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
How plugins, modules, capabilities and contributions relate.
|
|
4
|
+
|
|
5
|
+
The short version: a **plugin** is a bag of **modules**; a module declares which **capabilities** it
|
|
6
|
+
`requires` and `provides`, and its body returns **contributions**; the manager topologically orders
|
|
7
|
+
activation from those declarations and expands each contribution into the flat entries the
|
|
8
|
+
**capability registry** stores.
|
|
9
|
+
|
|
10
|
+
## Domain model
|
|
11
|
+
|
|
12
|
+
```mermaid
|
|
13
|
+
classDiagram
|
|
14
|
+
direction TB
|
|
15
|
+
|
|
16
|
+
class Plugin {
|
|
17
|
+
+Meta meta
|
|
18
|
+
+PluginModule[] modules
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class PluginModule {
|
|
22
|
+
+string id
|
|
23
|
+
+ActivationSpec activation
|
|
24
|
+
+activate(props) Effect
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class ActivationSpec {
|
|
28
|
+
+mode dependency | event
|
|
29
|
+
+AnyTag[] requires
|
|
30
|
+
+AnyTag[] provides
|
|
31
|
+
+Events activatesOn
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class Module {
|
|
35
|
+
+AnyTag[] requires
|
|
36
|
+
+AnyTag[] provides
|
|
37
|
+
+Events activatesOn
|
|
38
|
+
+call(options) Effect
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class Tag {
|
|
42
|
+
+arity single
|
|
43
|
+
+yield* gives T
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class MultiTag {
|
|
47
|
+
+arity multi
|
|
48
|
+
+yield* gives Contributions
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class Contribution {
|
|
52
|
+
+AnyTag capability
|
|
53
|
+
+unknown[] values
|
|
54
|
+
+deactivate()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class CapabilityEntry {
|
|
58
|
+
+InterfaceDef interface
|
|
59
|
+
+T implementation
|
|
60
|
+
+deactivate()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class Contributions {
|
|
64
|
+
+Atom atom
|
|
65
|
+
+get() T[]
|
|
66
|
+
+subscribe(cb)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class CapabilityManager {
|
|
70
|
+
+contribute(entry)
|
|
71
|
+
+get(tag) T
|
|
72
|
+
+getAll(tag) T[]
|
|
73
|
+
+atom(tag) Atom
|
|
74
|
+
+contributions(tag) Contributions
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class PluginManager {
|
|
78
|
+
+start()
|
|
79
|
+
+activate(event)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Plugin "1" *-- "0..n" PluginModule : modules
|
|
83
|
+
PluginModule --> ActivationSpec : activation
|
|
84
|
+
Module ..> PluginModule : Plugin.addModule()
|
|
85
|
+
ActivationSpec ..> Tag : requires / provides
|
|
86
|
+
ActivationSpec ..> MultiTag : requires / provides
|
|
87
|
+
Tag ..> Contribution : Capability.contribute()
|
|
88
|
+
MultiTag ..> Contribution : Capability.contributeAll()
|
|
89
|
+
Contribution ..> CapabilityEntry : expandContributions() 1 to n
|
|
90
|
+
CapabilityManager "1" o-- "0..n" CapabilityEntry : registry keyed by NSID
|
|
91
|
+
CapabilityManager ..> Contributions : live view for a MultiTag
|
|
92
|
+
PluginManager --> PluginModule : orders and activates
|
|
93
|
+
PluginManager --> CapabilityManager : owns
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### The two-layer split
|
|
97
|
+
|
|
98
|
+
`Contribution` and `CapabilityEntry` (exported as `Capability.Any`) sit on either side of the
|
|
99
|
+
registry boundary — the authoring layer and the storage layer:
|
|
100
|
+
|
|
101
|
+
| | `Contribution` (authoring) | `Capability.Any` (registry) |
|
|
102
|
+
| ------------ | ---------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
103
|
+
| Produced by | `Capability.contribute` / `contributeAll` | `CapabilityManager.expandContributions` |
|
|
104
|
+
| Holds | **n** values for one capability | exactly **one** implementation |
|
|
105
|
+
| Identity | branded by capability identity (NSID + arity) — powers the `EnsureProvides` completeness check | the `InterfaceDef` only |
|
|
106
|
+
| Service type | erased to `AnyTag`, so a body's service type never leaks into `.d.ts` emit | names `T` directly |
|
|
107
|
+
|
|
108
|
+
`expandContributions` is the one-way bridge: a `contributeAll(tag, [a, b, c])` is one `Contribution`
|
|
109
|
+
that becomes three registry entries, with the `deactivate` hook pinned to the first so it runs
|
|
110
|
+
exactly once.
|
|
111
|
+
|
|
112
|
+
## Activation
|
|
113
|
+
|
|
114
|
+
```mermaid
|
|
115
|
+
flowchart TB
|
|
116
|
+
subgraph authoring["Authoring (compile time)"]
|
|
117
|
+
A1["Capability.make / makeSingleton<br/>defines a Tag"]
|
|
118
|
+
A2["Capability.lazyModule / inlineModule<br/>or an AppCapability maker<br/>→ Capability.Module"]
|
|
119
|
+
A3["Plugin.define(meta).pipe(<br/>Plugin.addModule(...), Plugin.make)<br/>→ Plugin"]
|
|
120
|
+
A1 --> A2 --> A3
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
subgraph ordering["Ordering (PluginManager.start)"]
|
|
124
|
+
B1["Collect every enabled plugin's modules"]
|
|
125
|
+
B2{"activation.mode"}
|
|
126
|
+
B3["dependency:<br/>edges from requires → provides<br/>Kahn topological waves"]
|
|
127
|
+
B4["event:<br/>parked until activatesOn fires,<br/>requires pulled on demand"]
|
|
128
|
+
B1 --> B2
|
|
129
|
+
B2 -->|dependency| B3
|
|
130
|
+
B2 -->|event| B4
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
subgraph runtime["Activation + registry"]
|
|
134
|
+
C1["Build the module's Context<br/>from its requires"]
|
|
135
|
+
C2["activate(props) → Contribution[]"]
|
|
136
|
+
C3["Validate against declared provides"]
|
|
137
|
+
C4["expandContributions → Capability.Any[]"]
|
|
138
|
+
C5["CapabilityManager.contribute(entry)"]
|
|
139
|
+
C1 --> C2 --> C3 --> C4 --> C5
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
subgraph consume["Consumption"]
|
|
143
|
+
D1["In a module body:<br/>yield* Tag"]
|
|
144
|
+
D2["Dynamically:<br/>Capability.get / getAll / atom"]
|
|
145
|
+
D3["Reactively:<br/>Contributions.atom"]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
A3 --> B1
|
|
149
|
+
B3 --> C1
|
|
150
|
+
B4 --> C1
|
|
151
|
+
C5 --> D1
|
|
152
|
+
C5 --> D2
|
|
153
|
+
C5 --> D3
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Failure modes are structural rather than fatal: a dependency cycle, a duplicate singleton provider,
|
|
157
|
+
or an unsatisfiable `requires` puts the owning plugin into an error state and excludes it from
|
|
158
|
+
further rounds, while everything else proceeds. A consumer whose provider is event-gated **waits**
|
|
159
|
+
rather than erroring, and activates in a later round once the provider contributes.
|
|
160
|
+
|
|
161
|
+
### Singleton vs multi
|
|
162
|
+
|
|
163
|
+
Arity is a property of the tag and decides what `yield*` gives you:
|
|
164
|
+
|
|
165
|
+
- `Capability.makeSingleton<T>()(nsid)` → `Tag`. `yield* tag` gives `T`. Requiring one gates
|
|
166
|
+
activation on its single provider; two providers is a `DuplicateProviderError`.
|
|
167
|
+
- `Capability.make<T>()(nsid)` → `MultiTag` (the default). `yield* tag` gives a live
|
|
168
|
+
`Contributions<T>` collection. Providers never gate consumers, so consume it reactively — a
|
|
169
|
+
one-shot snapshot silently misses late contributions.
|
|
170
|
+
|
|
171
|
+
Both forms are curried so the NSID string literal is captured and brands the identifier; the NSID
|
|
172
|
+
must be camelCase (`DXN.Name`).
|
|
173
|
+
|
|
174
|
+
## Where things live
|
|
175
|
+
|
|
176
|
+
| Concept | File |
|
|
177
|
+
| -------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
178
|
+
| Tags, arity, `Contribution`, `contribute`, module makers | `src/core/capability.ts` |
|
|
179
|
+
| `Plugin`, `PluginModule`, `ActivationSpec`, the builder | `src/core/plugin.ts` |
|
|
180
|
+
| Public manager API, unit composition, shutdown, failure policy | `src/core/plugin-manager/plugin-manager.ts` |
|
|
181
|
+
| Shared substrate: atoms, status channel, fibers (`ManagerState`) | `src/core/plugin-manager/manager-state.ts` |
|
|
182
|
+
| Catalog: add/enable/disable, lazy resolution, dependency closure | `src/core/plugin-manager/plugin-catalog.ts` |
|
|
183
|
+
| Lifecycle engine: start/activate/deactivate/reset, rounds, waves | `src/core/plugin-manager/activation-scheduler.ts` |
|
|
184
|
+
| Ordering logic (runnable selection, waves, cycles), on `@dxos/graph` | `src/core/plugin-manager/activation-graph.ts` |
|
|
185
|
+
| Memoized module loads, provides validation, deactivation | `src/core/plugin-manager/module-loader.ts` |
|
|
186
|
+
| The NSID-keyed registry and its reactive views | `src/core/capability-manager.ts` |
|
|
187
|
+
| Tagged errors (cycle, missing/duplicate provider, mismatch) | `src/core/errors.ts` |
|
|
188
|
+
| Per-capability module makers (`surface`, `settings`, …) | `@dxos/app-toolkit` `src/app-framework/AppCapability.ts` |
|
|
189
|
+
|
|
190
|
+
Worked examples: `@dxos/plugin-client` and `@dxos/plugin-markdown`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @dxos/app-framework
|
|
2
2
|
|
|
3
|
+
## 1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0280a6a: Omitting `activatesOn` on a plugin module now puts it in the **idle** wave rather than the startup wave. A module that must run at boot has to declare `activatesOn: ActivationEvents.Startup` explicitly.
|
|
8
|
+
|
|
9
|
+
This is a behaviour change for out-of-repo plugin authors: an un-annotated module that previously ran during startup now runs at host idle. Un-annotated modules remain pullable as providers, so one that a startup module `requires` is still activated ahead of its own wave — the change is only visible for modules nothing on the boot path depends on.
|
|
10
|
+
|
|
11
|
+
The `@dxos/app-toolkit` maker families that back the app shell — `settings`, `operationHandler`, `reactContext`, `reactRoot`, `navigationResolver` and `navigationHandler` — now state `Startup` explicitly, so modules built with them are unaffected. `appGraphBuilder` (idle) and `skillDefinition` (assistant start) were already explicit.
|
|
12
|
+
|
|
13
|
+
- 34a8433: Order module activation by capability dependencies instead of hand-wired events.
|
|
14
|
+
A module declares the capabilities it `requires` and `provides` (or a runtime
|
|
15
|
+
`activatesOn` event) and the plugin manager topologically orders activation from
|
|
16
|
+
that graph. Capabilities are yieldable Effect services, so accessing an undeclared
|
|
17
|
+
capability or omitting a declared one is now a type error, and missing providers,
|
|
18
|
+
dependency cycles, and duplicate providers fail fast with tagged errors instead of
|
|
19
|
+
runtime assertions. Plugins compose as a flat chain of `Plugin.addModule` over
|
|
20
|
+
module bodies authored with `Capability.lazyModule` (code-split) or
|
|
21
|
+
`Capability.inlineModule` (eager), or with a per-capability maker from the new
|
|
22
|
+
`AppCapability` namespace (`surface`, `settings`, `appGraphBuilder`, `translations`,
|
|
23
|
+
`schema`, ...) that bakes in the module name and default provides. A module is an
|
|
24
|
+
opaque `Capability.Module<Options>`, parameterized only by its options type, so a
|
|
25
|
+
module export never leaks a foreign capability's type into declaration emit.
|
|
26
|
+
|
|
27
|
+
Every plugin in the repository is migrated to this API. The plugins gain no
|
|
28
|
+
behaviour of their own from the change, but any plugin defined outside the
|
|
29
|
+
repository must be migrated too — the legacy API is removed, not deprecated.
|
|
30
|
+
|
|
31
|
+
Breaking: the legacy event-wiring API is removed — `AppPlugin` and its
|
|
32
|
+
`addXModule` helpers, `firesBeforeActivation`/`firesAfterActivation`, `compatFires`,
|
|
33
|
+
and the ordering-only `Setup*`/`*Ready` activation events (genuine runtime events
|
|
34
|
+
remain). `Capability.provide`/`provideAll` are renamed to
|
|
35
|
+
`Capability.contribute`/`contributeAll`, and the untyped raw builder
|
|
36
|
+
`Capability.contributes` is removed. Multi is now the default capability arity:
|
|
37
|
+
`Capability.make` defines a multi (registry) capability and
|
|
38
|
+
`Capability.makeSingleton` the single-provider case, both curried
|
|
39
|
+
(`make<T>()(nsid)`) so the NSID literal brands the identifier. The
|
|
40
|
+
`withPluginManager` `capabilities` test option now accepts `Contribution[]`.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 4a0b78b: Bundler-plugin entrypoints no longer publish a `source` export condition: `@dxos/app-framework/vite-plugin` and `@dxos/ui-theme/plugin`. These run in Node inside a `vite.config.ts` and reach `node:*`, so a `source` condition let an app's `source`-first resolver pull their Node-only sources into a browser bundle.
|
|
45
|
+
|
|
46
|
+
Default resolution is unchanged — both entrypoints already resolved to their built `dist` for ordinary consumers, with the same exports and runtime behaviour. Only resolution under `--conditions=source` changes: it now yields the built output, matching `@dxos/config`'s bundler-plugin entrypoints.
|
|
47
|
+
|
|
48
|
+
- Updated dependencies [85ad256]
|
|
49
|
+
- Updated dependencies [557e243]
|
|
50
|
+
- Updated dependencies [557e243]
|
|
51
|
+
- Updated dependencies [7c426d4]
|
|
52
|
+
- Updated dependencies [fc83abd]
|
|
53
|
+
- Updated dependencies [77d0026]
|
|
54
|
+
- @dxos/protocols@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
55
|
+
- @dxos/react-ui@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
56
|
+
- @dxos/compute@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
57
|
+
- @dxos/react-hooks@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
58
|
+
- @dxos/compute-runtime@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
59
|
+
- @dxos/edge-client@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
60
|
+
- @dxos/operation@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
61
|
+
- @dxos/async@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
62
|
+
- @dxos/context@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
63
|
+
- @dxos/debug@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
64
|
+
- @dxos/effect@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
65
|
+
- @dxos/errors@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
66
|
+
- @dxos/graph@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
67
|
+
- @dxos/invariant@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
68
|
+
- @dxos/keys@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
69
|
+
- @dxos/log@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
70
|
+
- @dxos/react-error-boundary@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
71
|
+
- @dxos/ui-theme@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
72
|
+
- @dxos/util@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
73
|
+
- @dxos/web-context@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
74
|
+
- @dxos/web-context-react@1.0.0-next-bbabc02363f18d7643ea4160e3a7a7809870fd0d
|
|
75
|
+
|
|
3
76
|
## 0.11.1
|
|
4
77
|
|
|
5
78
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A framework for building applications from composible plugins.
|
|
4
4
|
|
|
5
|
+
See [API.md](./API.md) for the API surface, and [ARCHITECTURE.md](./ARCHITECTURE.md) for how
|
|
6
|
+
plugins, modules, capabilities and contributions relate and how activation is ordered.
|
|
7
|
+
|
|
5
8
|
## Installation
|
|
6
9
|
|
|
7
10
|
```bash
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { ReactSurface } from "./common/capabilities.mjs";
|
|
2
|
+
import { SurfacesRequested } from "./common/activation-events.mjs";
|
|
2
3
|
import { Position } from "@dxos/util";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
3
5
|
import { Atom } from "@effect-atom/atom";
|
|
6
|
+
import { EffectEx } from "@dxos/effect";
|
|
4
7
|
import { log } from "@dxos/log";
|
|
8
|
+
import * as Exit from "effect/Exit";
|
|
5
9
|
import { Fragment, Profiler, Suspense, createContext, createElement, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
10
|
import { ErrorBoundary } from "@dxos/react-error-boundary";
|
|
7
11
|
import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
|
|
@@ -37,9 +41,8 @@ var PluginManagerContext = createContext(void 0);
|
|
|
37
41
|
*/
|
|
38
42
|
var usePluginManager = () => useContext(PluginManagerContext) ?? raise(/* @__PURE__ */ new Error("Missing PluginManagerContext"));
|
|
39
43
|
/**
|
|
40
|
-
* Get the plugin manager
|
|
41
|
-
*
|
|
42
|
-
* enhancement rather than a hard requirement.
|
|
44
|
+
* Get the plugin manager, or undefined outside a provider. Surfaces and activation signals fire
|
|
45
|
+
* demand events opportunistically and must not throw when rendered standalone (stories, tests).
|
|
43
46
|
*/
|
|
44
47
|
var useOptionalPluginManager = () => useContext(PluginManagerContext);
|
|
45
48
|
/**
|
|
@@ -607,6 +610,16 @@ var emptySnapshot = () => EMPTY_SNAPSHOT;
|
|
|
607
610
|
//#endregion
|
|
608
611
|
//#region src/ui/components/Surface/SurfaceComponent.tsx
|
|
609
612
|
var DEFAULT_PLACEHOLDER = /* @__PURE__ */ jsx(Fragment, {});
|
|
613
|
+
/**
|
|
614
|
+
* Fires the role's surface demand event so modules gated on it load (see the `roles` option of
|
|
615
|
+
* the surface module maker). Safe to call repeatedly: the surface manager claims the first
|
|
616
|
+
* demand per role, which keeps mount cost flat where Surface instances are numerous (every card
|
|
617
|
+
* in a grid mounts one) instead of re-entering the scheduler.
|
|
618
|
+
*/
|
|
619
|
+
var requestSurfaces = (surfaceManager, manager, role) => {
|
|
620
|
+
if (!manager || !surfaceManager.requestRole(role)) return;
|
|
621
|
+
EffectEx.runDetached(manager.activate(SurfacesRequested(role)).pipe(Effect.onExit((exit) => Exit.isFailure(exit) ? Effect.sync(() => surfaceManager.releaseRole(role)) : Effect.void)));
|
|
622
|
+
};
|
|
610
623
|
var WebComponentWrapper = memo(({ id, role, data, limit, definition, ...rest }) => {
|
|
611
624
|
const containerRef = useRef(null);
|
|
612
625
|
const elementRef = useRef(null);
|
|
@@ -732,7 +745,16 @@ var SurfaceComponent = memo(({ id: _id, type, data: dataProp, limit, placeholder
|
|
|
732
745
|
const data = useDefaultValue(dataProp, () => ({}));
|
|
733
746
|
const surfaceManager = useSurfaceManager();
|
|
734
747
|
const effectiveRole = type?.role ?? "";
|
|
735
|
-
const
|
|
748
|
+
const roleCandidates = useAtomValue(surfaceManager.candidatesAtom(effectiveRole));
|
|
749
|
+
const pluginManager = useOptionalPluginManager();
|
|
750
|
+
useEffect(() => {
|
|
751
|
+
requestSurfaces(surfaceManager, pluginManager, effectiveRole);
|
|
752
|
+
}, [
|
|
753
|
+
surfaceManager,
|
|
754
|
+
pluginManager,
|
|
755
|
+
effectiveRole
|
|
756
|
+
]);
|
|
757
|
+
const definitions = matchCandidates(roleCandidates, effectiveRole, data);
|
|
736
758
|
const candidates = limit != null ? definitions.slice(0, limit) : definitions;
|
|
737
759
|
const truncated = limit != null && definitions.length > limit;
|
|
738
760
|
const churnRef = useRef({
|
|
@@ -793,12 +815,19 @@ var matchCandidates = (definitions, role, data) => {
|
|
|
793
815
|
*/
|
|
794
816
|
var useIsSurfaceAvailable = () => {
|
|
795
817
|
const surfaceManager = useSurfaceManager();
|
|
818
|
+
const pluginManager = useOptionalPluginManager();
|
|
796
819
|
const registry = useContext(RegistryContext);
|
|
797
820
|
return useCallback((args) => {
|
|
798
821
|
const effectiveRole = args.type?.role;
|
|
799
822
|
if (effectiveRole == null) return false;
|
|
800
|
-
|
|
801
|
-
|
|
823
|
+
const candidates = matchCandidates(registry.get(surfaceManager.candidatesAtom(effectiveRole)), effectiveRole, args.data);
|
|
824
|
+
if (candidates.length === 0) requestSurfaces(surfaceManager, pluginManager, effectiveRole);
|
|
825
|
+
return candidates.length > 0;
|
|
826
|
+
}, [
|
|
827
|
+
surfaceManager,
|
|
828
|
+
pluginManager,
|
|
829
|
+
registry
|
|
830
|
+
]);
|
|
802
831
|
};
|
|
803
832
|
//#endregion
|
|
804
833
|
//#region src/ui/components/Surface/types.ts
|
|
@@ -930,6 +959,7 @@ var SurfaceManager = class {
|
|
|
930
959
|
return bucket ? Data.array(bucket) : EMPTY_CANDIDATES;
|
|
931
960
|
}).pipe(Atom.keepAlive));
|
|
932
961
|
#warnedInvalidIds = /* @__PURE__ */ new Set();
|
|
962
|
+
#requestedRoles = /* @__PURE__ */ new Set();
|
|
933
963
|
constructor(capabilities) {
|
|
934
964
|
this.#capabilities = capabilities;
|
|
935
965
|
}
|
|
@@ -937,6 +967,25 @@ var SurfaceManager = class {
|
|
|
937
967
|
candidatesAtom(role) {
|
|
938
968
|
return this.#candidates(role);
|
|
939
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Claims the first surface demand for a role, returning `true` only to that first caller so
|
|
972
|
+
* it can fire the activation event. Subsequent calls — re-renders, repeated availability
|
|
973
|
+
* checks — return `false`.
|
|
974
|
+
*/
|
|
975
|
+
requestRole(role) {
|
|
976
|
+
if (role === "" || this.#requestedRoles.has(role)) return false;
|
|
977
|
+
this.#requestedRoles.add(role);
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Returns a claimed role to the pool. The claim is taken before the dispatch is known to have
|
|
982
|
+
* happened, so a dispatch that fails or is interrupted (shutdown, a failed activation) must give
|
|
983
|
+
* it back — nothing else re-requests the role, and `useIsSurfaceAvailable`'s own retry is
|
|
984
|
+
* claimed away too, leaving every surface in that role permanently empty.
|
|
985
|
+
*/
|
|
986
|
+
releaseRole(role) {
|
|
987
|
+
this.#requestedRoles.delete(role);
|
|
988
|
+
}
|
|
940
989
|
/** Drops definitions with an invalid local id, warning once per id. */
|
|
941
990
|
#dropInvalid(definitions) {
|
|
942
991
|
return definitions.filter((definition) => {
|
|
@@ -946,7 +995,7 @@ var SurfaceManager = class {
|
|
|
946
995
|
log.warn("dropping surface with invalid id; the final segment must be camelCase (no hyphens or underscores)", { id: definition.id }, {
|
|
947
996
|
"~LogMeta": "~LogMeta",
|
|
948
997
|
F: __dxlog_file,
|
|
949
|
-
L:
|
|
998
|
+
L: 115,
|
|
950
999
|
S: this
|
|
951
1000
|
});
|
|
952
1001
|
}
|