@dxos/app-framework 0.14.0 → 0.16.0
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/CHANGELOG.md +73 -0
- package/dist/lib/App.mjs +1 -2
- package/dist/lib/Hooks.mjs +1 -2
- package/dist/lib/chunk-App.mjs +11 -0
- package/dist/lib/chunk-App.mjs.map +1 -0
- package/dist/lib/chunk-Cli.mjs +8 -0
- package/dist/lib/chunk-Cli.mjs.map +1 -0
- package/dist/lib/chunk-Devtools.mjs +112 -0
- package/dist/lib/chunk-Devtools.mjs.map +1 -0
- package/dist/lib/chunk-Hooks2.mjs +35 -0
- package/dist/lib/chunk-Hooks2.mjs.map +1 -0
- package/dist/lib/chunk-PluginManagerProvider.mjs +12 -0
- package/dist/lib/chunk-PluginManagerProvider.mjs.map +1 -0
- package/dist/lib/chunk-ProcessManagerPlugin.mjs +3 -3
- package/dist/lib/chunk-ProcessManagerPlugin.mjs.map +1 -1
- package/dist/lib/chunk-Role.mjs +19 -0
- package/dist/lib/chunk-Role.mjs.map +1 -0
- package/dist/lib/chunk-SharedPackages.mjs +11 -0
- package/dist/lib/chunk-SharedPackages.mjs.map +1 -0
- package/dist/lib/chunk-Surface.mjs.map +1 -1
- package/dist/lib/chunk-Surface2.mjs +37 -0
- package/dist/lib/chunk-Surface2.mjs.map +1 -0
- package/dist/lib/chunk-SurfaceManager.mjs +3 -3
- package/dist/lib/chunk-SurfaceManager.mjs.map +1 -1
- package/dist/lib/chunk-activation-event.mjs +86 -0
- package/dist/lib/chunk-activation-event.mjs.map +1 -0
- package/dist/lib/chunk-activation-events.mjs +66 -0
- package/dist/lib/chunk-activation-events.mjs.map +1 -0
- package/dist/lib/chunk-capabilities.mjs +202 -0
- package/dist/lib/chunk-capabilities.mjs.map +1 -0
- package/dist/lib/chunk-capability-manager.mjs +203 -0
- package/dist/lib/chunk-capability-manager.mjs.map +1 -0
- package/dist/lib/chunk-capability2.mjs +2 -2
- package/dist/lib/chunk-capability2.mjs.map +1 -1
- package/dist/lib/chunk-history-tracker.mjs +1 -1
- package/dist/lib/chunk-hooks.mjs +5 -5
- package/dist/lib/chunk-hooks.mjs.map +1 -1
- package/dist/lib/chunk-plugin-asset-cache.mjs +28 -0
- package/dist/lib/chunk-plugin-asset-cache.mjs.map +1 -0
- package/dist/lib/chunk-plugin-manager.mjs +68 -35
- package/dist/lib/chunk-plugin-manager.mjs.map +1 -1
- package/dist/lib/chunk-plugin-manager2.mjs +22 -0
- package/dist/lib/chunk-plugin-manager2.mjs.map +1 -0
- package/dist/lib/chunk-plugin-manifest.mjs +126 -0
- package/dist/lib/chunk-plugin-manifest.mjs.map +1 -0
- package/dist/lib/chunk-plugin.mjs +354 -0
- package/dist/lib/chunk-plugin.mjs.map +1 -0
- package/dist/lib/chunk-process-manager-capability.mjs +11 -10
- package/dist/lib/chunk-process-manager-capability.mjs.map +1 -1
- package/dist/lib/chunk-registry.mjs.map +1 -1
- package/dist/lib/chunk-undo-mapping.mjs +47 -0
- package/dist/lib/{plugin-process-manager/history/undo-mapping.mjs.map → chunk-undo-mapping.mjs.map} +1 -1
- package/dist/lib/chunk-undo-registry.mjs +22 -0
- package/dist/lib/{plugin-process-manager/history/undo-registry.mjs.map → chunk-undo-registry.mjs.map} +1 -1
- package/dist/lib/chunk-url-loader.mjs +345 -0
- package/dist/lib/chunk-url-loader.mjs.map +1 -0
- package/dist/lib/cli.mjs +3 -3
- package/dist/lib/cli.mjs.map +1 -1
- package/dist/lib/common/Role.mjs +1 -19
- package/dist/lib/common/activation-events.mjs +1 -57
- package/dist/lib/common/capabilities.mjs +1 -202
- package/dist/lib/config.mjs +1 -2
- package/dist/lib/core/activation-event.mjs +1 -86
- package/dist/lib/core/capability-manager.mjs +1 -203
- package/dist/lib/core/capability.mjs +1 -2
- package/dist/lib/core/plugin-asset-cache.mjs +1 -28
- package/dist/lib/core/plugin-manager.mjs +2 -19
- package/dist/lib/core/plugin-manifest.mjs +1 -126
- package/dist/lib/core/plugin.mjs +1 -354
- package/dist/lib/core/registry.mjs +1 -2
- package/dist/lib/core/url-loader.mjs +1 -345
- package/dist/lib/index.mjs +19 -19
- package/dist/lib/ns/App.mjs +2 -11
- package/dist/lib/ns/Cli.mjs +2 -8
- package/dist/lib/ns/Devtools.mjs +1 -112
- package/dist/lib/ns/Hooks.mjs +2 -35
- package/dist/lib/ns/PluginManagerProvider.mjs +2 -12
- package/dist/lib/ns/SharedPackages.mjs +2 -11
- package/dist/lib/ns/Surface.mjs +2 -35
- package/dist/lib/ns/Translations.mjs +1 -2
- package/dist/lib/plugin-cli.mjs +2 -4
- package/dist/lib/plugin-cli.mjs.map +1 -1
- package/dist/lib/plugin-process-manager/ProcessManagerPlugin.mjs +1 -2
- package/dist/lib/plugin-process-manager/history/history-tracker.mjs +1 -2
- package/dist/lib/plugin-process-manager/history/undo-mapping.mjs +1 -47
- package/dist/lib/plugin-process-manager/history/undo-registry.mjs +1 -22
- package/dist/lib/testing/react.mjs +1 -1
- package/dist/lib/testing/react.mjs.map +1 -1
- package/dist/lib/testing.mjs +5 -5
- package/dist/lib/testing.mjs.map +1 -1
- package/dist/lib/vite-plugin.mjs +3 -3
- package/dist/lib/vite-plugin.mjs.map +1 -1
- package/dist/types/src/common/activation-events.d.ts +8 -0
- package/dist/types/src/common/activation-events.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager/manager-state.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager/module-loader.d.ts +0 -10
- package/dist/types/src/core/plugin-manager/module-loader.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +32 -32
- package/src/common/activation-events.ts +9 -0
- package/src/core/plugin-manager/manager-state.ts +14 -12
- package/src/core/plugin-manager/module-loader.ts +43 -7
- package/src/core/plugin-manager/plugin-manager.ts +2 -1
- package/src/plugin-process-manager/process-manager-capability.test.ts +66 -1
- package/src/plugin-process-manager/process-manager-capability.ts +22 -2
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +9 -7
- package/src/ui/hooks/useApp.tsx +2 -1
- package/src/vite-plugin/boot-loader/loader-app/boot-loader.css +19 -1
- package/dist/lib/common/Role.mjs.map +0 -1
- package/dist/lib/common/activation-events.mjs.map +0 -1
- package/dist/lib/common/capabilities.mjs.map +0 -1
- package/dist/lib/core/activation-event.mjs.map +0 -1
- package/dist/lib/core/capability-manager.mjs.map +0 -1
- package/dist/lib/core/plugin-asset-cache.mjs.map +0 -1
- package/dist/lib/core/plugin-manager.mjs.map +0 -1
- package/dist/lib/core/plugin-manifest.mjs.map +0 -1
- package/dist/lib/core/plugin.mjs.map +0 -1
- package/dist/lib/core/url-loader.mjs.map +0 -1
- package/dist/lib/ns/App.mjs.map +0 -1
- package/dist/lib/ns/Cli.mjs.map +0 -1
- package/dist/lib/ns/Devtools.mjs.map +0 -1
- package/dist/lib/ns/Hooks.mjs.map +0 -1
- package/dist/lib/ns/PluginManagerProvider.mjs.map +0 -1
- package/dist/lib/ns/SharedPackages.mjs.map +0 -1
- package/dist/lib/ns/Surface.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/app-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "A framework for building applications from composible plugins.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -171,50 +171,50 @@
|
|
|
171
171
|
"esbuild": "0.28.2",
|
|
172
172
|
"idb-keyval": "^6.2.1",
|
|
173
173
|
"solid-js": "^1.9.11",
|
|
174
|
-
"@dxos/async": "0.
|
|
175
|
-
"@dxos/compute": "0.
|
|
176
|
-
"@dxos/compute-runtime": "0.
|
|
177
|
-
"@dxos/
|
|
178
|
-
"@dxos/
|
|
179
|
-
"@dxos/
|
|
180
|
-
"@dxos/
|
|
181
|
-
"@dxos/
|
|
182
|
-
"@dxos/
|
|
183
|
-
"@dxos/
|
|
184
|
-
"@dxos/
|
|
185
|
-
"@dxos/
|
|
186
|
-
"@dxos/
|
|
187
|
-
"@dxos/
|
|
188
|
-
"@dxos/
|
|
189
|
-
"@dxos/react-
|
|
190
|
-
"@dxos/
|
|
191
|
-
"@dxos/
|
|
192
|
-
"@dxos/
|
|
193
|
-
"@dxos/web-context": "0.
|
|
194
|
-
"@dxos/
|
|
174
|
+
"@dxos/async": "0.16.0",
|
|
175
|
+
"@dxos/compute": "0.16.0",
|
|
176
|
+
"@dxos/compute-runtime": "0.16.0",
|
|
177
|
+
"@dxos/debug": "0.16.0",
|
|
178
|
+
"@dxos/echo": "0.16.0",
|
|
179
|
+
"@dxos/edge-client": "0.16.0",
|
|
180
|
+
"@dxos/effect": "0.16.0",
|
|
181
|
+
"@dxos/errors": "0.16.0",
|
|
182
|
+
"@dxos/graph": "0.16.0",
|
|
183
|
+
"@dxos/invariant": "0.16.0",
|
|
184
|
+
"@dxos/keys": "0.16.0",
|
|
185
|
+
"@dxos/log": "0.16.0",
|
|
186
|
+
"@dxos/operation": "0.16.0",
|
|
187
|
+
"@dxos/protocols": "0.16.0",
|
|
188
|
+
"@dxos/react-error-boundary": "0.16.0",
|
|
189
|
+
"@dxos/react-hooks": "0.16.0",
|
|
190
|
+
"@dxos/ui-theme": "0.16.0",
|
|
191
|
+
"@dxos/util": "0.16.0",
|
|
192
|
+
"@dxos/web-context": "0.16.0",
|
|
193
|
+
"@dxos/web-context-react": "0.16.0",
|
|
194
|
+
"@dxos/context": "0.16.0"
|
|
195
195
|
},
|
|
196
196
|
"devDependencies": {
|
|
197
|
-
"@effect/atom-react": "4.0.
|
|
198
|
-
"@effect/vitest": "4.0.
|
|
197
|
+
"@effect/atom-react": "4.0.4",
|
|
198
|
+
"@effect/vitest": "4.0.4",
|
|
199
199
|
"@storybook/react-vite": "^10.6.0",
|
|
200
200
|
"@testing-library/react": "^16.3.2",
|
|
201
201
|
"@types/react": "~19.2.17",
|
|
202
202
|
"@typescript/typescript6": "^6.0.2",
|
|
203
|
-
"effect": "4.0.
|
|
203
|
+
"effect": "4.0.4",
|
|
204
204
|
"react": "~19.2.7",
|
|
205
205
|
"react-dom": "~19.2.7",
|
|
206
206
|
"storybook-solidjs-vite": "^10.7.1",
|
|
207
207
|
"typedoc": "0.28.1",
|
|
208
|
-
"vite": "^8.
|
|
209
|
-
"@dxos/brand": "0.
|
|
210
|
-
"@dxos/random": "0.
|
|
211
|
-
"@dxos/react-ui": "0.
|
|
212
|
-
"@dxos/react-ui-list": "0.
|
|
208
|
+
"vite": "^8.3.4",
|
|
209
|
+
"@dxos/brand": "0.16.0",
|
|
210
|
+
"@dxos/random": "0.16.0",
|
|
211
|
+
"@dxos/react-ui": "0.16.0",
|
|
212
|
+
"@dxos/react-ui-list": "0.16.0"
|
|
213
213
|
},
|
|
214
214
|
"peerDependencies": {
|
|
215
|
-
"@effect/atom-react": "4.0.
|
|
215
|
+
"@effect/atom-react": "4.0.4",
|
|
216
216
|
"@testing-library/react": "^16.3.2",
|
|
217
|
-
"effect": "4.0.
|
|
217
|
+
"effect": "4.0.4",
|
|
218
218
|
"react": "~19.2.7",
|
|
219
219
|
"react-dom": "~19.2.7"
|
|
220
220
|
},
|
|
@@ -38,6 +38,15 @@ export const CommandsRequested = ActivationEvent$.make('org.dxos.app-framework.e
|
|
|
38
38
|
*/
|
|
39
39
|
export const SpaceTemplatesRequested = ActivationEvent$.make('org.dxos.app-framework.event.spaceTemplatesRequested');
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Demand signal for plugin assets (each plugin's bundled `PLUGIN.mdl`). Fired by whoever is about to
|
|
43
|
+
* read them — plugin-code's graph builder when a plugin's spec node is resolved.
|
|
44
|
+
*
|
|
45
|
+
* Nothing reads a spec while the app boots, so the modules carrying them are gated here rather than
|
|
46
|
+
* running with every plugin's startup. Fire and await this before a one-shot read.
|
|
47
|
+
*/
|
|
48
|
+
export const PluginAssetsRequested = ActivationEvent$.make('org.dxos.app-framework.event.pluginAssetsRequested');
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* Fired once by the host at main-thread idle after the app is interactive.
|
|
43
52
|
*
|
|
@@ -322,21 +322,19 @@ export class ManagerState {
|
|
|
322
322
|
* Tracks fibers forked by the manager's units so shutdown can interrupt in-flight work.
|
|
323
323
|
*/
|
|
324
324
|
export class FiberTracker {
|
|
325
|
-
readonly #fibers =
|
|
325
|
+
readonly #fibers = new Set<Fiber.Fiber<unknown, unknown>>();
|
|
326
326
|
|
|
327
327
|
track(fiber: Fiber.Fiber<unknown, unknown>): Effect.Effect<void> {
|
|
328
|
-
return
|
|
328
|
+
return Effect.sync(() => void this.#fibers.add(fiber));
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
untrack(fiber: Fiber.Fiber<unknown, unknown>): Effect.Effect<void> {
|
|
332
|
-
return
|
|
332
|
+
return Effect.sync(() => void this.#fibers.delete(fiber));
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
/** Fire-and-forget: fork the effect and track the fiber until it settles. */
|
|
336
336
|
fork(effect: Effect.Effect<unknown>): void {
|
|
337
|
-
|
|
338
|
-
Effect.runSync(this.track(fiber));
|
|
339
|
-
Effect.runFork(Fiber.await(fiber).pipe(Effect.andThen(() => this.untrack(fiber))));
|
|
337
|
+
this.#trackUntilSettled(Effect.runFork(effect));
|
|
340
338
|
}
|
|
341
339
|
|
|
342
340
|
/**
|
|
@@ -345,19 +343,23 @@ export class FiberTracker {
|
|
|
345
343
|
* cannot supply.
|
|
346
344
|
*/
|
|
347
345
|
trackForked(fiber: Fiber.Fiber<unknown, unknown>): Effect.Effect<void> {
|
|
348
|
-
return Effect.
|
|
349
|
-
yield* this.track(fiber);
|
|
350
|
-
yield* Effect.forkDetach(Fiber.await(fiber).pipe(Effect.andThen(() => this.untrack(fiber))));
|
|
351
|
-
});
|
|
346
|
+
return Effect.sync(() => this.#trackUntilSettled(fiber));
|
|
352
347
|
}
|
|
353
348
|
|
|
354
349
|
interruptAll(): Effect.Effect<void> {
|
|
355
350
|
return Effect.gen({ self: this }, function* () {
|
|
356
|
-
const fibers =
|
|
351
|
+
const fibers = [...this.#fibers];
|
|
352
|
+
this.#fibers.clear();
|
|
357
353
|
yield* Effect.forEach(fibers, (fiber) => Fiber.interrupt(fiber), { concurrency: 'unbounded', discard: true });
|
|
358
|
-
yield* Ref.set(this.#fibers, []);
|
|
359
354
|
});
|
|
360
355
|
}
|
|
356
|
+
|
|
357
|
+
// An exit observer rather than a forked `Fiber.await`: this runs per module activation, and each
|
|
358
|
+
// fork costs a scheduler hop (a clamped `setTimeout` in the browser).
|
|
359
|
+
#trackUntilSettled(fiber: Fiber.Fiber<unknown, unknown>): void {
|
|
360
|
+
this.#fibers.add(fiber);
|
|
361
|
+
fiber.addObserver(() => this.#fibers.delete(fiber));
|
|
362
|
+
}
|
|
361
363
|
}
|
|
362
364
|
|
|
363
365
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import * as Cause from 'effect/Cause';
|
|
6
|
+
import * as Clock from 'effect/Clock';
|
|
6
7
|
import * as Context from 'effect/Context';
|
|
7
8
|
import * as Deferred from 'effect/Deferred';
|
|
8
9
|
import * as Duration from 'effect/Duration';
|
|
@@ -69,7 +70,16 @@ export const yieldToHost: Effect.Effect<void> = Effect.suspend(() => {
|
|
|
69
70
|
* as a typed requirement (`R = Plugin.Service`) rather than holding a manager reference — the
|
|
70
71
|
* manager satisfies it once at its public boundary.
|
|
71
72
|
*/
|
|
73
|
+
const SLOW_MODULE_WARNING = Duration.seconds(10);
|
|
74
|
+
const SLOW_MODULE_CHECK_INTERVAL = Duration.seconds(1);
|
|
75
|
+
|
|
76
|
+
type InFlightActivation = { moduleId: string; started: number; warned: boolean };
|
|
77
|
+
|
|
72
78
|
export class ModuleLoader {
|
|
79
|
+
// One watcher fiber for every in-flight activation rather than a sleeping fiber per module: a boot
|
|
80
|
+
// activates hundreds of modules, and each fiber's fork and timer cost main-thread time.
|
|
81
|
+
readonly #inFlight = new Set<InFlightActivation>();
|
|
82
|
+
#watchingSlow = false;
|
|
73
83
|
readonly #memo = new Map<Plugin.PluginModule['id'], Deferred.Deferred<Capability.Any[], Error>>();
|
|
74
84
|
readonly #semaphores = new Map<Plugin.PluginModule['id'], Semaphore.Semaphore>();
|
|
75
85
|
readonly #scopes = new Map<string, Scope.Closeable>();
|
|
@@ -389,13 +399,8 @@ export class ModuleLoader {
|
|
|
389
399
|
}).pipe(
|
|
390
400
|
Effect.tapCause(() => Scope.close(scope, Exit.void)),
|
|
391
401
|
Effect.withSpan('ModuleLoader.load', { attributes: { 'dx.module.id': module.id } }),
|
|
392
|
-
|
|
393
|
-
Effect.
|
|
394
|
-
Effect.andThen(
|
|
395
|
-
Effect.sync(() => log.warn(`module is taking a long time to activate`, { module: module.id })),
|
|
396
|
-
),
|
|
397
|
-
),
|
|
398
|
-
),
|
|
402
|
+
(activation) =>
|
|
403
|
+
Effect.flatMap(this.#watchSlow(module.id), (settled) => activation.pipe(Effect.ensuring(settled))),
|
|
399
404
|
Performance.addTrackEntry({
|
|
400
405
|
name: module.id,
|
|
401
406
|
devtools: {
|
|
@@ -541,6 +546,37 @@ export class ModuleLoader {
|
|
|
541
546
|
return normalizedError;
|
|
542
547
|
});
|
|
543
548
|
}
|
|
549
|
+
|
|
550
|
+
/** Registers an in-flight activation for the slow-module warning; the returned effect deregisters it. */
|
|
551
|
+
#watchSlow(moduleId: string): Effect.Effect<Effect.Effect<void>> {
|
|
552
|
+
return Effect.gen({ self: this }, function* () {
|
|
553
|
+
const activation: InFlightActivation = { moduleId, started: yield* Clock.currentTimeMillis, warned: false };
|
|
554
|
+
this.#inFlight.add(activation);
|
|
555
|
+
if (!this.#watchingSlow) {
|
|
556
|
+
this.#watchingSlow = true;
|
|
557
|
+
yield* this.#state.fibers.trackForked(yield* Effect.forkDetach(this.#slowWatcher()));
|
|
558
|
+
}
|
|
559
|
+
return Effect.sync(() => void this.#inFlight.delete(activation));
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
#slowWatcher(): Effect.Effect<void> {
|
|
564
|
+
return Effect.gen({ self: this }, function* () {
|
|
565
|
+
for (;;) {
|
|
566
|
+
yield* Effect.sleep(SLOW_MODULE_CHECK_INTERVAL);
|
|
567
|
+
if (this.#inFlight.size === 0) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const now = yield* Clock.currentTimeMillis;
|
|
571
|
+
for (const activation of this.#inFlight) {
|
|
572
|
+
if (!activation.warned && now - activation.started >= Duration.toMillis(SLOW_MODULE_WARNING)) {
|
|
573
|
+
activation.warned = true;
|
|
574
|
+
log.warn('module is taking a long time to activate', { module: activation.moduleId });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}).pipe(Effect.ensuring(Effect.sync(() => (this.#watchingSlow = false))));
|
|
579
|
+
}
|
|
544
580
|
}
|
|
545
581
|
|
|
546
582
|
/**
|
|
@@ -398,7 +398,8 @@ class ManagerImpl implements PluginManager {
|
|
|
398
398
|
Effect.flatMap((subscription) =>
|
|
399
399
|
PubSub.take(subscription).pipe(
|
|
400
400
|
Effect.tap((message) => Effect.sync(() => this._autoDisableOnModuleError(message))),
|
|
401
|
-
|
|
401
|
+
// A yield per message is a clamped 4ms browser timer; `take` already suspends when the queue is empty.
|
|
402
|
+
Effect.forever({ disableYield: true }),
|
|
402
403
|
),
|
|
403
404
|
),
|
|
404
405
|
Effect.scoped,
|
|
@@ -9,9 +9,11 @@ import * as Effect from 'effect/Effect';
|
|
|
9
9
|
import * as Exit from 'effect/Exit';
|
|
10
10
|
import * as Layer from 'effect/Layer';
|
|
11
11
|
import * as Registry from 'effect/reactivity/AtomRegistry';
|
|
12
|
+
import * as Schema from 'effect/Schema';
|
|
12
13
|
|
|
13
|
-
import { RemoteProcessManager } from '@dxos/compute-runtime';
|
|
14
|
+
import { RemoteOperationInvoker, RemoteProcessManager } from '@dxos/compute-runtime';
|
|
14
15
|
import * as LayerSpec from '@dxos/compute/LayerSpec';
|
|
16
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
15
17
|
import * as Process from '@dxos/compute/Process';
|
|
16
18
|
import * as ServiceResolver from '@dxos/compute/ServiceResolver';
|
|
17
19
|
import * as Trace from '@dxos/compute/Trace';
|
|
@@ -346,6 +348,69 @@ describe('one process manager', () => {
|
|
|
346
348
|
}),
|
|
347
349
|
);
|
|
348
350
|
|
|
351
|
+
const Double = Operation.make({
|
|
352
|
+
meta: { key: DXN.make('com.example.operation.test.double'), name: 'Double' },
|
|
353
|
+
input: Schema.Struct({ value: Schema.Number }),
|
|
354
|
+
output: Schema.Number,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
/** A plugin contributing a remote operation invoker that doubles `value`, recording each call, as EDGE's route would. */
|
|
358
|
+
const RemoteOperations = (calls: Array<{ deployedId: string; input: unknown; spaceId?: SpaceId }>) =>
|
|
359
|
+
Plugin.make(
|
|
360
|
+
Plugin.define(remoteMeta).pipe(
|
|
361
|
+
Plugin.addModule({
|
|
362
|
+
id: 'remote-operation-invoker',
|
|
363
|
+
activatesOn: ActivationEvents.Startup,
|
|
364
|
+
provides: [Capabilities.LayerSpec],
|
|
365
|
+
activate: () =>
|
|
366
|
+
Effect.succeed([
|
|
367
|
+
Capability.contribute(
|
|
368
|
+
Capabilities.LayerSpec,
|
|
369
|
+
LayerSpec.make(
|
|
370
|
+
{ affinity: 'application', requires: [], provides: [RemoteOperationInvoker.Service] },
|
|
371
|
+
() =>
|
|
372
|
+
Layer.succeed(RemoteOperationInvoker.Service, {
|
|
373
|
+
invoke: (_ctx, deployedId, input, options) =>
|
|
374
|
+
Effect.sync(() => {
|
|
375
|
+
calls.push({ deployedId, input, ...(options?.spaceId ? { spaceId: options.spaceId } : {}) });
|
|
376
|
+
return Schema.decodeUnknownSync(Double.input)(input).value * 2;
|
|
377
|
+
}),
|
|
378
|
+
}),
|
|
379
|
+
),
|
|
380
|
+
),
|
|
381
|
+
]),
|
|
382
|
+
}),
|
|
383
|
+
),
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
it.effect("runs the app's on:edge invocations as one call to the remote invoker a plugin contributes", () =>
|
|
387
|
+
Effect.gen(function* () {
|
|
388
|
+
const calls: Array<{ deployedId: string; input: unknown; spaceId?: SpaceId }> = [];
|
|
389
|
+
const manager = makeManager({ plugins: [RemoteOperations(calls)()], enabled: [remoteMeta.profile.key] });
|
|
390
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
391
|
+
|
|
392
|
+
const invoker = yield* manager.capabilities.waitFor(Capabilities.OperationInvoker);
|
|
393
|
+
const spaceId = edge.location.space;
|
|
394
|
+
expect(yield* invoker.invoke(Double, { value: 21 }, { on: 'edge', spaceId })).toEqual(42);
|
|
395
|
+
expect(calls).toEqual([{ deployedId: String(Double.meta.key), input: { value: 21 }, spaceId }]);
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
it.effect('refuses an on:edge invocation when no remote runtime is configured', () =>
|
|
400
|
+
Effect.gen(function* () {
|
|
401
|
+
const manager = makeManager({ plugins: [], enabled: [] });
|
|
402
|
+
yield* manager.activate(ActivationEvents.Startup);
|
|
403
|
+
|
|
404
|
+
const invoker = yield* manager.capabilities.waitFor(Capabilities.OperationInvoker);
|
|
405
|
+
const exit = yield* Effect.exit(
|
|
406
|
+
invoker.invoke(Double, { value: 1 }, { on: 'edge', spaceId: edge.location.space }),
|
|
407
|
+
);
|
|
408
|
+
expect(Exit.isFailure(exit) && String(Cause.squash(exit.cause))).toContain(
|
|
409
|
+
'No remote operation invoker configured',
|
|
410
|
+
);
|
|
411
|
+
}),
|
|
412
|
+
);
|
|
413
|
+
|
|
349
414
|
it.effect('falls back to no remote control without such a plugin, and keeps local control', () =>
|
|
350
415
|
Effect.gen(function* () {
|
|
351
416
|
const manager = makeManager({ plugins: [], enabled: [] });
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
LayerStack,
|
|
18
18
|
ProcessManager,
|
|
19
19
|
ProcessOperationInvoker,
|
|
20
|
+
RemoteOperationInvoker,
|
|
20
21
|
RemoteProcessManager,
|
|
21
22
|
RemoteTraceMonitor,
|
|
22
23
|
UnifiedProcessManager,
|
|
@@ -205,18 +206,21 @@ export default Capability.makeModule(
|
|
|
205
206
|
),
|
|
206
207
|
);
|
|
207
208
|
|
|
208
|
-
// Fallbacks for a host with no remote runtime; a plugin spec providing
|
|
209
|
+
// Fallbacks for a host with no remote runtime; a plugin spec providing any of these tags (e.g. EDGE's) wins.
|
|
209
210
|
const fallbacks = Effect.runSync(
|
|
210
211
|
Effect.gen(function* () {
|
|
211
212
|
const remoteProcessManager = yield* RemoteProcessManager.Service;
|
|
213
|
+
const remoteOperationInvoker = yield* RemoteOperationInvoker.Service;
|
|
212
214
|
const remoteTraceMonitor = yield* RemoteTraceMonitor.Service;
|
|
213
215
|
return Context.make(RemoteProcessManager.Service, remoteProcessManager).pipe(
|
|
216
|
+
Context.add(RemoteOperationInvoker.Service, remoteOperationInvoker),
|
|
214
217
|
Context.add(RemoteTraceMonitor.Service, remoteTraceMonitor),
|
|
215
218
|
);
|
|
216
219
|
}).pipe(
|
|
217
220
|
Effect.provide(
|
|
218
221
|
Layer.mergeAll(
|
|
219
222
|
RemoteProcessManager.layerNoop,
|
|
223
|
+
RemoteOperationInvoker.layerNoop,
|
|
220
224
|
// Remote ephemeral trace (DX-1125): the first contributed swarm-backed monitor, else no-op.
|
|
221
225
|
remoteTraceMonitors.length > 0
|
|
222
226
|
? Layer.succeed(RemoteTraceMonitor.Service, remoteTraceMonitors[0])
|
|
@@ -287,9 +291,25 @@ export default Capability.makeModule(
|
|
|
287
291
|
yield* Effect.addFinalizer(() => Scope.close(stackScope, Exit.void));
|
|
288
292
|
const unifiedProcessManager = fromStack(serviceResolver, stackScope, atomRegistry);
|
|
289
293
|
const unifiedProcessManagerLayer = Layer.succeed(Process.ManagerService, unifiedProcessManager);
|
|
294
|
+
// The stack's remote invoker, resolved per call for the same reason as the manager: it may need the client.
|
|
295
|
+
const remoteOperationInvoker: RemoteOperationInvoker.Invoker = {
|
|
296
|
+
invoke: (ctx, deployedId, input, options) =>
|
|
297
|
+
serviceResolver.resolve(RemoteOperationInvoker.Service, {}).pipe(
|
|
298
|
+
Scope.provide(stackScope),
|
|
299
|
+
Effect.orDie,
|
|
300
|
+
Effect.flatMap((remote) => remote.invoke(ctx, deployedId, input, options)),
|
|
301
|
+
),
|
|
302
|
+
};
|
|
290
303
|
const operationInvokerLayer = ProcessOperationInvoker.layer.pipe(
|
|
291
304
|
// Operations invoked through the app's own invoker are the person's actions, from a menu, dialog or shortcut.
|
|
292
|
-
Layer.provide(
|
|
305
|
+
Layer.provide(
|
|
306
|
+
Layer.mergeAll(
|
|
307
|
+
unifiedProcessManagerLayer,
|
|
308
|
+
baseLayer,
|
|
309
|
+
Layer.succeed(Database.Origin, 'user'),
|
|
310
|
+
Layer.succeed(RemoteOperationInvoker.Service, remoteOperationInvoker),
|
|
311
|
+
),
|
|
312
|
+
),
|
|
293
313
|
);
|
|
294
314
|
|
|
295
315
|
const runtimeLayer = Layer.mergeAll(
|
|
@@ -143,13 +143,15 @@ const DefaultStory = ({ debug: debugProp }: StoryArgs) => {
|
|
|
143
143
|
/>
|
|
144
144
|
<div className='overflow-y-auto h-full'>
|
|
145
145
|
<Listbox.Root items={surfaces.map((surface) => ({ value: surface.id, label: surface.id }))}>
|
|
146
|
-
<Listbox.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<Listbox.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
<Listbox.Viewport>
|
|
147
|
+
<Listbox.Content aria-label='Surfaces'>
|
|
148
|
+
{surfaces.map((surface) => (
|
|
149
|
+
<Listbox.Item key={surface.id} id={surface.id}>
|
|
150
|
+
<Listbox.ItemText classNames='flex items-center'>{surface.id}</Listbox.ItemText>
|
|
151
|
+
</Listbox.Item>
|
|
152
|
+
))}
|
|
153
|
+
</Listbox.Content>
|
|
154
|
+
</Listbox.Viewport>
|
|
153
155
|
</Listbox.Root>
|
|
154
156
|
</div>
|
|
155
157
|
</Panel.Body>
|
package/src/ui/hooks/useApp.tsx
CHANGED
|
@@ -437,7 +437,8 @@ export const useApp = ({
|
|
|
437
437
|
}
|
|
438
438
|
}),
|
|
439
439
|
),
|
|
440
|
-
|
|
440
|
+
// Each yield is a clamped 4ms timer in the browser, so a yielding loop drains a startup backlog one message per tick.
|
|
441
|
+
Effect.forever({ disableYield: true }),
|
|
441
442
|
),
|
|
442
443
|
);
|
|
443
444
|
|
|
@@ -121,7 +121,9 @@ body {
|
|
|
121
121
|
align-items: center;
|
|
122
122
|
justify-content: center;
|
|
123
123
|
filter: grayscale(100%);
|
|
124
|
-
transition:
|
|
124
|
+
transition:
|
|
125
|
+
filter 1000ms ease-out,
|
|
126
|
+
opacity 1000ms ease-out;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
/*
|
|
@@ -135,6 +137,22 @@ body {
|
|
|
135
137
|
filter: grayscale(0%);
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
/*
|
|
141
|
+
* On the light background a grey silhouette reads heavier than the brand colours, so the mark keeps its
|
|
142
|
+
* colours and is faded instead for the whole load; hover still shows it at full strength for inspection.
|
|
143
|
+
*/
|
|
144
|
+
@media (prefers-color-scheme: light) {
|
|
145
|
+
#boot-loader-mark,
|
|
146
|
+
#boot-loader-disc[data-host-driven] #boot-loader-mark {
|
|
147
|
+
filter: grayscale(0%);
|
|
148
|
+
opacity: 0.5;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#boot-loader-disc #boot-loader-mark:hover {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
138
156
|
#boot-loader-mark svg {
|
|
139
157
|
width: 100%;
|
|
140
158
|
height: 100%;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Role.mjs","names":[],"sources":["../../../src/common/Role.ts"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\n// @import-as-namespace\n\nimport type { DXN } from '@dxos/keys';\n\n/**\n * A typed role token. Carries a role NSID for runtime dispatch plus a phantom\n * data type so consumers using `type={SomeToken}` and providers using\n * `AppSurface.object(SomeToken, ...)` share a single type-level contract.\n *\n * Mint via {@link make}. The `role` field holds the bare NSID (e.g.\n * `org.dxos.role.article`) — the dispatch key, `data-role` attribute value,\n * and what `.role` comparisons inside matched components see.\n */\nexport type Role<TData> = {\n readonly role: string;\n /** Covariant phantom; never accessed at runtime. */\n readonly _phantom?: TData;\n};\n\n/**\n * Narrow data type carried by a role token.\n */\nexport type Data<T> = T extends Role<infer D> ? D : never;\n\n/**\n * Mints a typed role token. The NSID is validated at compile time via\n * {@link DXN.Name}: the final segment must be camelCase (no hyphens).\n * Dynamic NSIDs (template-literal / runtime strings) are validated at runtime.\n *\n * Identity is structural; all tokens with the same `role` string are\n * interchangeable at runtime.\n */\nexport const make: {\n <TData = unknown, T extends string = string>(\n nsid: [DXN.Name<T>] extends [never] ? `Invalid NSID \"${T}\": final segment must be camelCase (no hyphens)` : T,\n ): Role<TData>;\n} = <TData>(nsid: string): Role<TData> => {\n // Runtime guard for dynamic NSIDs that bypass the compile-time DXN.Name<T> check.\n if (\n typeof nsid !== 'string' ||\n !/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\\.[a-zA-Z][a-zA-Z0-9]{0,62})$/.test(\n nsid,\n )\n ) {\n throw new Error(\n `Invalid role NSID: \"${nsid}\". Must be a dotted name with a camelCase final segment (no hyphens in the last segment).`,\n );\n }\n return { role: nsid };\n};\n"],"mappings":";;;;;;;;;;;AAoCA,IAAa,QAID,SAA8B;CAExC,IACE,OAAO,SAAS,YAChB,CAAC,0HAA0H,KACzH,IACF,GAEA,MAAM,IAAI,MACR,uBAAuB,KAAK,0FAC9B;CAEF,OAAO,EAAE,MAAM,KAAK;AACtB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"activation-events.mjs","names":[],"sources":["../../../src/common/activation-events.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { ActivationEvent as ActivationEvent$ } from '../core/index.ts';\n\n/**\n * Fired when the app is started.\n * Defined in core; see {@link ActivationEvent$.Startup}.\n */\nexport const Startup = ActivationEvent$.Startup;\n\n/**\n * Demand signal for React surfaces, keyed by role NSID. Fired when a `Surface` for the role\n * first renders (and by availability checks that miss), so surface modules gated on their bound\n * roles load exactly when a screen region that can host them appears.\n */\nexport const SurfacesRequested = (role: string) =>\n ActivationEvent$.make('org.dxos.app-framework.event.surfacesRequested', role);\n\n/**\n * Demand signal for CLI commands. Fired by whoever is about to build a command tree — the `dx`\n * binary through {@link createCliApp}, the devtools terminal when its panel mounts — so command\n * modules load when someone can actually run a command.\n *\n * A command tree is read ONCE, so this must be fired and awaited before the read: a command\n * registered afterwards is simply absent, and `dx space list` falls through to the root help\n * rather than failing anywhere near the cause.\n */\nexport const CommandsRequested = ActivationEvent$.make('org.dxos.app-framework.event.commandsRequested');\n\n/**\n * Demand signal for space templates. Fired by whoever is about to offer a list of them — the create\n * space dialog, and the debug plugin's space generator when its panel mounts.\n *\n * Template content is bulky, so the modules carrying it are gated here rather than contributing at\n * startup. Like a command tree, the list is read once: fire and await this before reading it.\n */\nexport const SpaceTemplatesRequested = ActivationEvent$.make('org.dxos.app-framework.event.spaceTemplatesRequested');\n\n/**\n * Fired once by the host at main-thread idle after the app is interactive.\n *\n * For REGISTRATION contributions only — graph builders, operation handler sets, settings: the\n * app-wide indexes a user can reach from anywhere (the navtree, the operation registry, a\n * settings read) and which therefore no single surface can gate. A feature's own modules ride\n * its plugin start event, fired when its surface renders; putting a feature body here instead\n * is what turned the former `DeferredStartup` into a second startup pass.\n *\n * This is also what omitting `activatesOn` normalizes to, so a module that must run at boot has\n * to say `activatesOn: ActivationEvents.Startup` explicitly. Defined in core; see\n * {@link ActivationEvent$.Idle}.\n */\nexport const Idle = ActivationEvent$.Idle;\n"],"mappings":";;;;;;;;;;;;;;AAUA,IAAa,UAAU;;;;;;AAOvB,IAAa,qBAAqB,SAChC,KAAsB,kDAAkD,IAAI;;;;;;;;;;AAW9E,IAAa,oBAAoB,KAAsB,gDAAgD;;;;;;;;AASvG,IAAa,0BAA0B,KAAsB,sDAAsD;;;;;;;;;;;;;;AAenH,IAAa,OAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.mjs","names":[],"sources":["../../../src/common/capabilities.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\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 * as Option from 'effect/Option';\nimport type * as Atom from 'effect/reactivity/Atom';\nimport type * as Registry from 'effect/reactivity/AtomRegistry';\nimport type { FC, PropsWithChildren } from 'react';\n\nimport type {\n ProcessManager as ProcessManager$,\n RemoteTraceMonitor as RemoteTraceMonitor$,\n} from '@dxos/compute-runtime';\nimport * as ComputeLayerSpec from '@dxos/compute/LayerSpec';\nimport * as Operation from '@dxos/compute/Operation';\nimport * as OperationHandlerSet from '@dxos/compute/OperationHandlerSet';\nimport * as Process from '@dxos/compute/Process';\nimport * as ComputeServiceResolver from '@dxos/compute/ServiceResolver';\nimport * as Trace from '@dxos/compute/Trace';\nimport { OperationInvoker as OperationInvoker$ } from '@dxos/operation';\n\nimport { Capability as Capability$, Plugin as Plugin$, type PluginManager as PluginManager$ } from '../core/index.ts';\nimport type {\n HistoryTracker as HistoryTracker$,\n UndoMapping as UndoMapping$,\n UndoRegistry as UndoRegistry$,\n} from '../plugin-process-manager/index.ts';\nimport type { Surface } from '../ui/index.ts';\n\n/**\n * @category Capability\n */\nexport const PluginManager = Capability$.makeSingleton<PluginManager$.PluginManager>()(\n 'org.dxos.app-framework.capability.pluginManager',\n);\n\n/**\n * @category Capability\n */\nexport const AtomRegistry = Capability$.makeSingleton<Registry.AtomRegistry>()(\n 'org.dxos.app-framework.capability.atomRegistry',\n);\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.reactContext');\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.reactRoot');\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.capability.reactSurface');\n\n// The requirement channel stays open: a command's services are supplied partly by the contributing\n// plugin and partly by the host — `CommandConfig` carries the host's global flags and is provided as\n// an ambient layer by the host binary — so no single side can discharge them all. `CommandServices`\n// in @dxos/cli-util names what a host owes; hosts should type their layer with it.\nexport type AnyCommand = Command$.Command<any, 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 */\n// The input channel is closed: a contributed layer is merged into whatever a host is assembling, so\n// it has to carry its own requirements rather than expect the host to satisfy them.\nexport const Layer = Capability$.make<Layer$.Layer<any, any, never>>()('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<ComputeLayerSpec.LayerSpec>()('org.dxos.app-framework.capability.layerSpec');\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: ComputeServiceResolver.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.traceSink');\n\n/**\n * Source of ephemeral trace messages broadcast by remote runtimes over the space swarm (DX-1125).\n * Contributed by a client-aware plugin; the process-manager capability wires the first contribution\n * (or a no-op) into the aggregate {@link ProcessManager} so its `subscribeToTraceMessages` surfaces\n * remote progress alongside local.\n *\n * @category Capability\n */\nexport const RemoteTraceMonitor = Capability$.make<RemoteTraceMonitor$.Monitor>()(\n 'org.dxos.app-framework.capability.remoteTraceMonitor',\n);\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).pipe(\n * Layer.provide(Layer.succeed(ServiceResolver.ServiceResolver, resolver)),\n * ),\n * ),\n * );\n * ```\n *\n * @category Capability\n */\nexport const ServiceResolver = Capability$.makeSingleton<ComputeServiceResolver.ServiceResolver>()(\n 'org.dxos.app-framework.capability.serviceResolver',\n);\n\n/**\n * Process manager backing the shared {@link ProcessManagerRuntime}. Exposes the\n * live process tree (including inactive/terminated entries) via\n * {@link Process.Manager#processTreeAtom}.\n *\n * @category Capability\n */\nexport const ProcessManager = Capability$.makeSingleton<Process.Manager>()(\n 'org.dxos.app-framework.capability.processManager',\n);\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 | ComputeServiceResolver.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?: Effect.RunOptions,\n ): Fiber$.Fiber<A, E>;\n runSync<A, E>(effect: Effect.Effect<A, E, ProcessManagerRuntimeServices>): A;\n}\n\n/**\n * @category Capability\n */\nexport const ProcessManagerRuntime = Capability$.makeSingleton<ProcessManagerRuntime>()(\n 'org.dxos.app-framework.capability.processManagerRuntime',\n);\n\nexport type ManagedRuntime = ManagedRuntime$.ManagedRuntime<any, any>;\n\n/**\n * @category Capability\n */\nexport const ManagedRuntime = Capability$.makeSingleton<ManagedRuntime>()(\n 'org.dxos.app-framework.capability.managedRuntime',\n);\n\n//\n// Operation System Capabilities\n//\n\nexport const OperationHandler = Capability$.make<OperationHandlerSet.OperationHandlerSet>()(\n 'org.dxos.app-framework.capability.operationHandler',\n);\n\n/**\n * Merged, contribution-ordered view over all {@link OperationHandler} contributions — the same\n * set the operation invoker resolves against. Provided by ProcessManagerPlugin.\n * @category Capability\n */\nexport const OperationHandlers = Capability$.makeSingleton<OperationHandlerSet.OperationHandlerSet>()(\n 'org.dxos.app-framework.capability.operationHandlers',\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.undoMapping');\n\n/**\n * Operation invoker backed by the process manager. Spawns a process per\n * operation invocation; see `ProcessOperationInvoker` in `@dxos/compute-runtime`.\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$.makeSingleton<OperationInvoker>()(\n 'org.dxos.app-framework.capability.operationInvoker',\n);\n\nexport type UndoRegistry = UndoRegistry$.UndoRegistry;\n\n/**\n * Undo registry - provided by ProcessManagerPlugin.\n * @category Capability\n */\nexport const UndoRegistry = Capability$.makeSingleton<UndoRegistry>()('org.dxos.app-framework.capability.undoRegistry');\n\nexport type HistoryTracker = HistoryTracker$.HistoryTracker;\n\n/**\n * History tracker - provided by ProcessManagerPlugin.\n * @category Capability\n */\nexport const HistoryTracker = Capability$.makeSingleton<HistoryTracker>()(\n 'org.dxos.app-framework.capability.historyTracker',\n);\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 * Get the current value of an atom capability, or `Option.none()` when either the registry or the\n * atom itself is uncontributed.\n *\n * For operations that run on both the app and a headless host (the edge operation-service, `dx mcp\n * serve`): those hosts have a capability manager but activate no UI plugins, so {@link getAtomValue}\n * fails on capabilities like `Layout` that only the app contributes.\n *\n * @example const layout = yield* Capabilities.getAtomValueOption(AppCapabilities.Layout);\n */\nexport const getAtomValueOption = <T>(\n atomCapability: Capability$.InterfaceDef<Atom.Atom<T>>,\n): Effect.Effect<Option.Option<T>, never, Capability$.Service> =>\n Effect.gen(function* () {\n const registry = yield* Capability$.getOption(AtomRegistry);\n const atom = yield* Capability$.getOption(atomCapability);\n return Option.map(Option.all([registry, atom]), ([registry, atom]) => 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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,IAAa,gBAAgB,cAAwD,CAAC,CACpF,iDACF;;;;AAKA,IAAa,eAAe,cAAiD,CAAC,CAC5E,gDACF;;;;AAWA,IAAa,eAAe,KAA+B,CAAC,CAAC,gDAAgD;;;;AAO7G,IAAa,YAAY,KAA4B,CAAC,CAAC,6CAA6C;;;;AAUpG,IAAa,eAAe,KAA+B,CAAC,CAAC,gDAAgD;;;;AAW7G,IAAa,UAAU,KAA6B,CAAC,CAAC,2CAA2C;;;;AAOjG,IAAa,QAAQ,KAAgD,CAAC,CAAC,yCAAyC;;;;;;;;;;AAWhH,IAAa,YAAY,KAA6C,CAAC,CAAC,6CAA6C;;;;;;;;;;;;;AAmCrH,IAAa,YAAY,KAAmC,CAAC,CAAC,6CAA6C;;;;;;;;;AAU3G,IAAa,qBAAqB,KAA8C,CAAC,CAC/E,sDACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kBAAkB,cAAkE,CAAC,CAChG,mDACF;;;;;;;;AASA,IAAa,iBAAiB,cAA2C,CAAC,CACxE,kDACF;;;;AAsCA,IAAa,wBAAwB,cAAiD,CAAC,CACrF,yDACF;;;;AAOA,IAAa,iBAAiB,cAA0C,CAAC,CACvE,kDACF;AAMA,IAAa,mBAAmB,KAA0D,CAAC,CACzF,oDACF;;;;;;AAOA,IAAa,oBAAoB,cAAmE,CAAC,CACnG,qDACF;;;;;AAQA,IAAa,cAAc,KAAgC,CAAC,CAAC,+CAA+C;;;;;AAY5G,IAAa,mBAAmB,cAA4C,CAAC,CAC3E,oDACF;;;;;AAQA,IAAa,eAAe,cAAwC,CAAC,CAAC,gDAAgD;;;;;AAQtH,IAAa,iBAAiB,cAA0C,CAAC,CACvE,kDACF;;;;;AAUA,IAAa,gBACX,mBAEA,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,IAAgB,YAAY;CACpD,MAAM,OAAO,OAAO,IAAgB,cAAc;CAClD,OAAO,SAAS,IAAI,IAAI;AAC1B,CAAC;;;;;;;;;;;AAYH,IAAa,sBACX,mBAEA,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,UAAsB,YAAY;CAC1D,MAAM,OAAO,OAAO,UAAsB,cAAc;CACxD,OAAO,OAAO,IAAI,OAAO,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,UAAU,UAAU,SAAS,IAAI,IAAI,CAAC;AAC1F,CAAC;;;;;AAMH,IAAa,mBACX,gBACA,OAEA,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,IAAgB,YAAY;CACpD,MAAM,OAAO,OAAO,IAAgB,cAAc;CAClD,SAAS,IAAI,MAAM,GAAG,SAAS,IAAI,IAAI,CAAC,CAAC;AAC3C,CAAC;;;;;AAMH,IAAa,iBACX,gBACA,aAEA,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,IAAgB,YAAY;CACpD,MAAM,OAAO,OAAO,IAAgB,cAAc;CAClD,OAAO,SAAS,UAAU,YAAY,SAAS,SAAS,IAAI,IAAI,CAAC,CAAC;AACpE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"activation-event.mjs","names":[],"sources":["../../../src/core/activation-event.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { DXN } from '@dxos/keys';\nimport { compositeKey } from '@dxos/util';\n\n/**\n * A unique DXN identifier representing an event.\n *\n * @example dxn:org.dxos.plugin.example.event.ready\n */\nexport type ActivationEvent = {\n id: DXN.DXN;\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 from an NSID.\n * Static NSID strings are validated at compile time via {@link DXN.Name}.\n */\nexport const make: {\n <T extends string>(\n nsid: [DXN.Name<T>] extends [never] ? `Invalid NSID \"${T}\": final segment must be camelCase (no hyphens)` : T,\n specifier?: string,\n ): ActivationEvent;\n} = (nsid: string, specifier?: string): ActivationEvent => ({\n id: DXN.make(nsid),\n specifier,\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\n/**\n * Fired when the app is started.\n * Defined in core (rather than the `common` well-known events, which re-export it) because\n * the plugin manager's `start()` delegates `activate(Startup)` and publishes the\n * startup-complete message on this key.\n * @deprecated As an `activatesOn` target — declare `provides`/`requires` instead; the\n * dependency pass replaces startup-event wiring. External callers keep using\n * `PluginManager.activate(Startup)` (it delegates to `start()`).\n */\nexport const Startup = make('org.dxos.app-framework.event.startup');\n\n/**\n * Fired once by the host at main-thread idle after the app is interactive; see the `common`\n * well-known events for the authoring-facing documentation.\n *\n * Defined here rather than only in `common` because omitting `activatesOn` normalizes to this\n * event, and `Plugin.normalizeActivation` lives in core — which cannot import `common` without\n * closing the `common -> core` cycle.\n */\nexport const Idle = make('org.dxos.app-framework.event.idle');\n\n/**\n * A plugin's feature-start event, by convention `<pluginKey>.event.start`. A plugin's\n * off-critical-path modules declare `activatesOn` on its own start event (conventionally\n * exported as `<Name>Events.Start` from the plugin's types); cross-plugin contributions (a\n * skill, a markdown extension) declare the CONSUMING plugin's start event. Deriving the id\n * from the key alone lets fire sites (enable, idle, demand signals) activate a feature\n * without importing its package.\n */\nexport const pluginStart = (pluginKey: string | DXN.DXN) => make(`${pluginKey}.event.start`);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6BA,IAAa,QAKR,MAAc,eAAyC;CAC1D,IAAI,IAAI,KAAK,IAAI;CACjB;AACF;;;;AAKA,IAAa,YAAY,UACvB,MAAM,YAAY,aAAa,MAAM,IAAI,MAAM,SAAS,IAAI,MAAM;;;;AAKpE,IAAa,SAAS,GAAG,YAA+B;CAAE,MAAM;CAAmB;AAAO;;;;AAK1F,IAAa,SAAS,GAAG,YAA+B;CAAE,MAAM;CAAmB;AAAO;;;;AAK1F,IAAa,WAAW,WACtB,UAAU,UAAU,OAAO,SAAS;;;;AAKtC,IAAa,WAAW,WACtB,UAAU,UAAU,OAAO,SAAS;;;;AAKtC,IAAa,aAAa,WAAoB,UAAU,SAAS,OAAO,SAAS,CAAC,MAAM;;;;;;;;;;AAWxF,IAAa,UAAU,KAAK,sCAAsC;;;;;;;;;AAUlE,IAAa,OAAO,KAAK,mCAAmC;;;;;;;;;AAU5D,IAAa,eAAe,cAAgC,KAAK,GAAG,UAAU,aAAa"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"capability-manager.mjs","names":[],"sources":["../../../src/core/capability-manager.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Deferred from 'effect/Deferred';\nimport * as Effect from 'effect/Effect';\nimport * as Atom from 'effect/reactivity/Atom';\nimport type * as Registry from 'effect/reactivity/AtomRegistry';\n\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { log } from '@dxos/log';\n\nimport type * as Capability from './capability.ts';\nimport { ContributionTypeId } from './capability.ts';\nimport { CapabilityNotFoundError } from './errors.ts';\n\ntype CapabilityEntry<T> = {\n moduleId: string;\n implementation: T;\n};\n\n//\n// Contribution ingestion — manager-side machinery for a module's activate result. Lives here\n// rather than in `capability` so the authoring namespace exposes only the plugin-author API.\n//\n\n/**\n * Type guard to check if a value is a {@link Capability.Contribution}.\n */\nexport const isContribution = (value: unknown): value is Capability.AnyContribution => {\n return typeof value === 'object' && value !== null && ContributionTypeId in value;\n};\n\n/**\n * Normalizes a module activate result into a flat list of items (legacy capabilities and\n * typed contributions).\n */\nexport const normalizeActivateResult = (\n result: Capability.ModuleReturn | Capability.AnyContribution | readonly Capability.AnyContribution[],\n): Array<Capability.Any | Capability.AnyContribution> => {\n if (result == null) {\n return [];\n }\n // Cast: Array.isArray does not narrow the ReadonlyArray members of ModuleReturn.\n return (Array.isArray(result) ? [...result] : [result]) as Array<Capability.Any | Capability.AnyContribution>;\n};\n\n/**\n * Expands typed contributions into per-value capability entries; legacy capability entries\n * pass through.\n */\nexport const expandContributions = (\n items: ReadonlyArray<Capability.Any | Capability.AnyContribution>,\n): Capability.Any[] =>\n items.flatMap((item) =>\n isContribution(item)\n ? item.values.map((value): Capability.Any => ({\n interface: item.capability,\n implementation: value,\n }))\n : [item],\n );\n\n/**\n * Options for creating a capability manager.\n * @internal\n */\nexport type CapabilityManagerOptions = {\n registry: Registry.AtomRegistry;\n};\n\n/**\n * Interface for the Capability Manager.\n * Provides methods for contributing, removing, and accessing capabilities.\n */\nexport interface CapabilityManager {\n contribute<T>(args: { module: string; interface: Capability.InterfaceDef<T>; implementation: T }): void;\n\n remove<T>(interfaceDef: Capability.InterfaceDef<T>, implementation: T): void;\n\n /**\n * Get the Atom reference to the available capabilities for a given interface.\n * Primarily useful for deriving other Atom values based on the capabilities or\n * for subscribing to changes in the capabilities.\n * @returns An atom reference to the available capabilities.\n */\n atom<T>(interfaceDef: Capability.InterfaceDef<T>): Atom.Atom<T[]>;\n\n /**\n * Get capabilities from the capability manager.\n * @returns An array of capabilities.\n */\n getAll<T>(interfaceDef: Capability.InterfaceDef<T>): T[];\n\n /**\n * Requests a single capability from the capability manager.\n * @returns The capability.\n * @throws If no capability is found.\n */\n get<T>(interfaceDef: Capability.InterfaceDef<T>): T;\n\n /**\n * Waits for a capability to be available.\n * @returns The capability.\n */\n waitFor<T>(interfaceDef: Capability.InterfaceDef<T>): Effect.Effect<T>;\n\n /**\n * Promise form of {@link waitFor}, memoized per interface so repeated calls return the SAME\n * reference — React dedupes suspended renders by thrown-promise identity, so a fresh promise per\n * render would re-suspend forever.\n * @returns A promise settling once at least one capability is contributed.\n */\n waitForPromise<T>(interfaceDef: Capability.InterfaceDef<T>): Promise<T>;\n\n /**\n * Get capabilities grouped by the module that contributed them.\n * @returns An atom containing a record from module ID to capability implementations.\n */\n atomByModule<T>(interfaceDef: Capability.InterfaceDef<T>): Atom.Atom<Record<string, T[]>>;\n\n /**\n * Live view over all contributions for a capability interface.\n * Stable per interface: repeated calls return the same object.\n */\n contributions<T>(interfaceDef: Capability.InterfaceDef<T>): Capability.Contributions<T>;\n\n /**\n * Lists capability interface identifiers that currently have at least one contribution.\n */\n listRegisteredIdentifiers(): string[];\n}\n\n/**\n * Internal implementation of CapabilityManager.\n */\nclass CapabilityManagerImpl implements CapabilityManager {\n private readonly _registry: Registry.AtomRegistry;\n\n private readonly _registeredIdentifiers = new Set<string>();\n\n private readonly _contributionsViews = new Map<string, Capability.Contributions<unknown>>();\n\n /** In-flight {@link waitForPromise} results, keyed by interface identifier. */\n private readonly _waitPromises = new Map<string, Promise<unknown>>();\n\n private readonly _capabilityEntries = Atom.family<string, Atom.Writable<CapabilityEntry<unknown>[]>>(() => {\n return Atom.make<CapabilityEntry<unknown>[]>([]).pipe(Atom.keepAlive);\n });\n\n readonly _capabilities = Atom.family<string, Atom.Atom<unknown[]>>((id: string) => {\n return Atom.make((get) => {\n const current = get(this._capabilityEntries(id));\n return current.map((c) => c.implementation);\n }).pipe(Atom.keepAlive);\n });\n\n readonly _capabilitiesByModule = Atom.family<string, Atom.Atom<Record<string, unknown[]>>>((id: string) => {\n return Atom.make((get) => {\n const entries = get(this._capabilityEntries(id));\n const result: Record<string, unknown[]> = {};\n for (const entry of entries) {\n (result[entry.moduleId] ??= []).push(entry.implementation);\n }\n return result;\n }).pipe(Atom.keepAlive);\n });\n\n readonly _capability = Atom.family<string, Atom.Atom<unknown>>((id: string) => {\n return Atom.make((get) => {\n const current = get(this._capabilities(id));\n if (current.length === 0) {\n throw new CapabilityNotFoundError({ identifier: id, registered: this.listRegisteredIdentifiers() });\n }\n return current[0];\n });\n });\n\n constructor({ registry }: CapabilityManagerOptions) {\n this._registry = registry;\n }\n\n contribute<T>({\n module: moduleId,\n interface: interfaceDef,\n implementation,\n }: {\n module: string;\n interface: Capability.InterfaceDef<T>;\n implementation: T;\n }): void {\n const current = this._registry.get(this._capabilityEntries(interfaceDef.identifier));\n const isDuplicate = current.some((c) => c.moduleId === moduleId && c.implementation === implementation);\n if (isDuplicate) {\n log('capability already contributed, skipping', { id: interfaceDef.identifier, moduleId });\n return;\n }\n\n const entry: CapabilityEntry<T> = { moduleId, implementation };\n this._registry.set(this._capabilityEntries(interfaceDef.identifier), [...current, entry]);\n this._registeredIdentifiers.add(interfaceDef.identifier);\n log('capability contributed', {\n id: interfaceDef.identifier,\n moduleId,\n count: current.length,\n });\n }\n\n remove<T>(interfaceDef: Capability.InterfaceDef<T>, implementation: T): void {\n const current = this._registry.get(this._capabilityEntries(interfaceDef.identifier));\n if (current.length === 0) {\n return;\n }\n\n const next = current.filter((c) => c.implementation !== implementation);\n if (next.length !== current.length) {\n this._registry.set(this._capabilityEntries(interfaceDef.identifier), next);\n if (next.length === 0) {\n this._registeredIdentifiers.delete(interfaceDef.identifier);\n }\n log('capability removed', { id: interfaceDef.identifier, count: current.length });\n } else {\n log.warn('capability not removed', { id: interfaceDef.identifier });\n }\n }\n\n atom<T>(interfaceDef: Capability.InterfaceDef<T>): Atom.Atom<T[]> {\n // NOTE: This the type-checking for capabilities is done at the time of contribution.\n return this._capabilities(interfaceDef.identifier) as Atom.Atom<T[]>;\n }\n\n getAll<T>(interfaceDef: Capability.InterfaceDef<T>): T[] {\n return this._registry.get(this.atom(interfaceDef));\n }\n\n get<T>(interfaceDef: Capability.InterfaceDef<T>): T {\n const capabilities = this.getAll(interfaceDef);\n if (capabilities.length === 0) {\n log('capability not available', {\n requested: interfaceDef.identifier,\n registered: this.listRegisteredIdentifiers(),\n });\n throw new CapabilityNotFoundError({\n identifier: interfaceDef.identifier,\n registered: this.listRegisteredIdentifiers(),\n });\n }\n return capabilities[0];\n }\n\n listRegisteredIdentifiers(): string[] {\n return [...this._registeredIdentifiers].sort();\n }\n\n waitFor<T>(interfaceDef: Capability.InterfaceDef<T>): Effect.Effect<T> {\n return Effect.gen({ self: this }, function* () {\n const [capability] = this.getAll(interfaceDef);\n if (capability) {\n return capability;\n }\n\n const deferred = yield* Deferred.make<T>();\n const cancel = this._registry.subscribe(this.atom(interfaceDef), (capabilities) => {\n if (capabilities.length > 0) {\n Effect.runSync(Deferred.succeed(deferred, capabilities[0]));\n }\n });\n const result = yield* Deferred.await(deferred);\n cancel();\n return result;\n });\n }\n\n waitForPromise<T>(interfaceDef: Capability.InterfaceDef<T>): Promise<T> {\n const existing = this._waitPromises.get(interfaceDef.identifier);\n if (existing) {\n return existing as Promise<T>;\n }\n const promise = EffectEx.runPromise(this.waitFor(interfaceDef)).finally(() => {\n // Drop it once settled so a capability that is removed and re-contributed can be waited on\n // again; until then every suspended reader shares this one reference.\n this._waitPromises.delete(interfaceDef.identifier);\n });\n this._waitPromises.set(interfaceDef.identifier, promise);\n return promise;\n }\n\n atomByModule<T>(interfaceDef: Capability.InterfaceDef<T>): Atom.Atom<Record<string, T[]>> {\n return this._capabilitiesByModule(interfaceDef.identifier) as Atom.Atom<Record<string, T[]>>;\n }\n\n contributions<T>(interfaceDef: Capability.InterfaceDef<T>): Capability.Contributions<T> {\n const existing = this._contributionsViews.get(interfaceDef.identifier);\n if (existing) {\n // NOTE: The type-checking for capabilities is done at the time of contribution.\n return existing as Capability.Contributions<T>;\n }\n\n const atom = this._capabilities(interfaceDef.identifier);\n const view: Capability.Contributions<unknown> = {\n atom,\n get: () => this._registry.get(atom),\n subscribe: (cb) => this._registry.subscribe(atom, cb),\n };\n this._contributionsViews.set(interfaceDef.identifier, view);\n return view as Capability.Contributions<T>;\n }\n}\n\n/**\n * Creates a new Capability Manager instance.\n */\nexport const make = (options: CapabilityManagerOptions): CapabilityManager => new CapabilityManagerImpl(options);\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,IAAa,kBAAkB,UAAwD;CACrF,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,sBAAsB;AAC9E;;;;;AAMA,IAAa,2BACX,WACuD;CACvD,IAAI,UAAU,MACZ,OAAO,CAAC;CAGV,OAAQ,MAAM,QAAQ,MAAM,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM;AACvD;;;;;AAMA,IAAa,uBACX,UAEA,MAAM,SAAS,SACb,eAAe,IAAI,IACf,KAAK,OAAO,KAAK,WAA2B;CAC1C,WAAW,KAAK;CAChB,gBAAgB;AAClB,EAAE,IACF,CAAC,IAAI,CACX;;;;AA2EF,IAAM,wBAAN,MAAyD;CACvD;CAEA,yCAA0C,IAAI,IAAY;CAE1D,sCAAuC,IAAI,IAA+C;;CAG1F,gCAAiC,IAAI,IAA8B;CAEnE,qBAAsC,KAAK,aAAgE;EACzG,OAAO,KAAK,KAAiC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS;CACtE,CAAC;CAED,gBAAyB,KAAK,QAAsC,OAAe;EACjF,OAAO,KAAK,MAAM,QAAQ;GAExB,OADgB,IAAI,KAAK,mBAAmB,EAAE,CACvC,CAAA,CAAQ,KAAK,MAAM,EAAE,cAAc;EAC5C,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS;CACxB,CAAC;CAED,wBAAiC,KAAK,QAAsD,OAAe;EACzG,OAAO,KAAK,MAAM,QAAQ;GACxB,MAAM,UAAU,IAAI,KAAK,mBAAmB,EAAE,CAAC;GAC/C,MAAM,SAAoC,CAAC;GAC3C,KAAK,MAAM,SAAS,SAClB,CAAC,OAAO,MAAM,cAAc,CAAC,EAAA,CAAG,KAAK,MAAM,cAAc;GAE3D,OAAO;EACT,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS;CACxB,CAAC;CAED,cAAuB,KAAK,QAAoC,OAAe;EAC7E,OAAO,KAAK,MAAM,QAAQ;GACxB,MAAM,UAAU,IAAI,KAAK,cAAc,EAAE,CAAC;GAC1C,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,wBAAwB;IAAE,YAAY;IAAI,YAAY,KAAK,0BAA0B;GAAE,CAAC;GAEpG,OAAO,QAAQ;EACjB,CAAC;CACH,CAAC;CAED,YAAY,EAAE,YAAsC;EAClD,KAAK,YAAY;CACnB;CAEA,WAAc,EACZ,QAAQ,UACR,WAAW,cACX,kBAKO;EACP,MAAM,UAAU,KAAK,UAAU,IAAI,KAAK,mBAAmB,aAAa,UAAU,CAAC;EAEnF,IADoB,QAAQ,MAAM,MAAM,EAAE,aAAa,YAAY,EAAE,mBAAmB,cACpF,GAAa;GACf,IAAI,4CAA4C;IAAE,IAAI,aAAa;IAAY;GAAS,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC;GACzF;EACF;EAEA,MAAM,QAA4B;GAAE;GAAU;EAAe;EAC7D,KAAK,UAAU,IAAI,KAAK,mBAAmB,aAAa,UAAU,GAAG,CAAC,GAAG,SAAS,KAAK,CAAC;EACxF,KAAK,uBAAuB,IAAI,aAAa,UAAU;EACvD,IAAI,0BAA0B;GAC5B,IAAI,aAAa;GACjB;GACA,OAAO,QAAQ;EACjB,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;CACH;CAEA,OAAU,cAA0C,gBAAyB;EAC3E,MAAM,UAAU,KAAK,UAAU,IAAI,KAAK,mBAAmB,aAAa,UAAU,CAAC;EACnF,IAAI,QAAQ,WAAW,GACrB;EAGF,MAAM,OAAO,QAAQ,QAAQ,MAAM,EAAE,mBAAmB,cAAc;EACtE,IAAI,KAAK,WAAW,QAAQ,QAAQ;GAClC,KAAK,UAAU,IAAI,KAAK,mBAAmB,aAAa,UAAU,GAAG,IAAI;GACzE,IAAI,KAAK,WAAW,GAClB,KAAK,uBAAuB,OAAO,aAAa,UAAU;GAE5D,IAAI,sBAAsB;IAAE,IAAI,aAAa;IAAY,OAAO,QAAQ;GAAO,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC;EAClF,OACE,IAAI,KAAK,0BAA0B,EAAE,IAAI,aAAa,WAAW,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC;CAEtE;CAEA,KAAQ,cAA0D;EAEhE,OAAO,KAAK,cAAc,aAAa,UAAU;CACnD;CAEA,OAAU,cAA+C;EACvD,OAAO,KAAK,UAAU,IAAI,KAAK,KAAK,YAAY,CAAC;CACnD;CAEA,IAAO,cAA6C;EAClD,MAAM,eAAe,KAAK,OAAO,YAAY;EAC7C,IAAI,aAAa,WAAW,GAAG;GAC7B,IAAI,4BAA4B;IAC9B,WAAW,aAAa;IACxB,YAAY,KAAK,0BAA0B;GAC7C,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC;GACD,MAAM,IAAI,wBAAwB;IAChC,YAAY,aAAa;IACzB,YAAY,KAAK,0BAA0B;GAC7C,CAAC;EACH;EACA,OAAO,aAAa;CACtB;CAEA,4BAAsC;EACpC,OAAO,CAAC,GAAG,KAAK,sBAAsB,CAAC,CAAC,KAAK;CAC/C;CAEA,QAAW,cAA4D;EACrE,OAAO,OAAO,IAAI,EAAE,MAAM,KAAK,GAAG,aAAa;GAC7C,MAAM,CAAC,cAAc,KAAK,OAAO,YAAY;GAC7C,IAAI,YACF,OAAO;GAGT,MAAM,WAAW,OAAO,SAAS,KAAQ;GACzC,MAAM,SAAS,KAAK,UAAU,UAAU,KAAK,KAAK,YAAY,IAAI,iBAAiB;IACjF,IAAI,aAAa,SAAS,GACxB,OAAO,QAAQ,SAAS,QAAQ,UAAU,aAAa,EAAE,CAAC;GAE9D,CAAC;GACD,MAAM,SAAS,OAAO,SAAS,MAAM,QAAQ;GAC7C,OAAO;GACP,OAAO;EACT,CAAC;CACH;CAEA,eAAkB,cAAsD;EACtE,MAAM,WAAW,KAAK,cAAc,IAAI,aAAa,UAAU;EAC/D,IAAI,UACF,OAAO;EAET,MAAM,UAAU,SAAS,WAAW,KAAK,QAAQ,YAAY,CAAC,CAAC,CAAC,cAAc;GAG5E,KAAK,cAAc,OAAO,aAAa,UAAU;EACnD,CAAC;EACD,KAAK,cAAc,IAAI,aAAa,YAAY,OAAO;EACvD,OAAO;CACT;CAEA,aAAgB,cAA0E;EACxF,OAAO,KAAK,sBAAsB,aAAa,UAAU;CAC3D;CAEA,cAAiB,cAAuE;EACtF,MAAM,WAAW,KAAK,oBAAoB,IAAI,aAAa,UAAU;EACrE,IAAI,UAEF,OAAO;EAGT,MAAM,OAAO,KAAK,cAAc,aAAa,UAAU;EACvD,MAAM,OAA0C;GAC9C;GACA,WAAW,KAAK,UAAU,IAAI,IAAI;GAClC,YAAY,OAAO,KAAK,UAAU,UAAU,MAAM,EAAE;EACtD;EACA,KAAK,oBAAoB,IAAI,aAAa,YAAY,IAAI;EAC1D,OAAO;CACT;AACF;;;;AAKA,IAAa,QAAQ,YAAyD,IAAI,sBAAsB,OAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-asset-cache.mjs","names":[],"sources":["../../../src/core/plugin-asset-cache.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { BaseError } from '@dxos/errors';\n\n/**\n * Tagged error for cache operations. Construction sites supply `context.operation`\n * (one of `'cache' | 'evict' | 'resolve' | 'list'`) and `context.pluginId` so\n * downstream handlers can route on the failed call without scanning the message.\n */\nexport class PluginAssetCacheError extends BaseError.extend(\n 'PluginAssetCacheError',\n 'Plugin asset cache operation failed',\n) {}\n\n/**\n * Per-platform offline cache for third-party plugin assets.\n *\n * Implementations live alongside their platform glue (composer-app's\n * `asset-cache/{tauri,service-worker}.ts`); the no-op default is for tests\n * and unsupported environments.\n */\nexport interface Cache {\n /**\n * Persist all listed URLs under the namespace of `pluginId`. Idempotent.\n * The order of `urls` is significant — the first entry is treated as the entry module.\n */\n cache(pluginId: string, urls: readonly string[]): Effect.Effect<void, PluginAssetCacheError>;\n\n /**\n * Drop all assets for a plugin (uninstall).\n */\n evict(pluginId: string): Effect.Effect<void, PluginAssetCacheError>;\n\n /**\n * Resolves a plugin asset URL to a platform-specific cached URL when one is available.\n * Returns the original URL when no cached copy exists or the platform serves cached\n * responses transparently (e.g. service worker fetch interception on web).\n */\n resolve(pluginId: string, url: string): Effect.Effect<string, PluginAssetCacheError>;\n\n /**\n * List currently cached plugin ids (diagnostic).\n */\n list(): Effect.Effect<readonly string[], PluginAssetCacheError>;\n}\n\n/**\n * No-op implementation. Used when no platform cache is registered (tests, unsupported environments).\n * Plugins still load — they just have no offline guarantee.\n */\nexport const noop = (): Cache => ({\n cache: () => Effect.void,\n evict: () => Effect.void,\n resolve: (_pluginId, url) => Effect.succeed(url),\n list: () => Effect.succeed([] as readonly string[]),\n});\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,wBAAb,cAA2C,UAAU,OACnD,yBACA,qCACF,CAAC,CAAC,CAAC;;;;;AAsCH,IAAa,cAAqB;CAChC,aAAa,OAAO;CACpB,aAAa,OAAO;CACpB,UAAU,WAAW,QAAQ,OAAO,QAAQ,GAAG;CAC/C,YAAY,OAAO,QAAQ,CAAC,CAAsB;AACpD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-manager.mjs","names":[],"sources":["../../../src/core/plugin-manager/index.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// The manager's public surface. The sibling modules (state, catalog, scheduler, graph,\n// loader) are internal collaborators — import them only from within this directory.\nexport * from './plugin-manager.ts';\nexport { PluginManagerContext as Context } from '../../context.ts';\nexport {\n DependencyCycleError,\n DuplicateProviderError,\n MissingProviderError,\n ModuleActivationError,\n ProvidesMismatchError,\n} from '../errors.ts';\n"],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-manifest.mjs","names":[],"sources":["../../../src/core/plugin-manifest.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as FetchHttpClient from 'effect/http/FetchHttpClient';\nimport * as HttpClient from 'effect/http/HttpClient';\nimport * as HttpClientRequest from 'effect/http/HttpClientRequest';\nimport * as HttpClientResponse from 'effect/http/HttpClientResponse';\nimport * as Schema from 'effect/Schema';\nimport * as Struct from 'effect/Struct';\n\nimport { BaseError } from '@dxos/errors';\nimport { PLUGIN_ENTRY_FILENAME, PluginManifestSchema } from '@dxos/protocols';\n\n/**\n * Default port the Vite plugin (`composerPlugin`) binds the dev server to.\n *\n * Shared single source of truth — `composerPlugin` reads it as the default\n * port, and the host's \"Load Dev Plugin\" affordance pre-fills the manifest URL\n * with `http://localhost:${DEV_SERVER_PORT}/manifest.json`. Lives in\n * app-framework rather than `@dxos/protocols` because the constant is a\n * client-side convention (host loader + Vite plugin) rather than a wire-level\n * protocol.\n */\nexport const DEV_SERVER_PORT = 3967;\n\n/**\n * Tagged error for manifest fetch / parse failures. Construction sites set\n * `context.manifestUrl` and `context.reason` (one of `'fetch-failed' |\n * 'http-error' | 'parse-failed' | 'invalid'`) so handlers can route on the\n * specific failure mode.\n */\nexport class PluginManifestError extends BaseError.extend('PluginManifestError', 'Plugin manifest is invalid') {}\n\n/**\n * Schema for a third-party plugin manifest.\n *\n * Production manifests are published as a sibling of the plugin's entry module\n * ({@link PLUGIN_ENTRY_FILENAME}) and advertise every file the plugin needs at\n * runtime so the host can eagerly cache them for offline use.\n *\n * Dev manifests (served by `vite dev` via {@link composerPlugin}) set `devEntry`\n * to point at the unbundled source entry. The host's loader treats the presence\n * of `devEntry` as the dev-mode signal: it imports the entry directly from the\n * dev server, skips eager asset caching, and skips static stylesheet injection\n * (Vite handles CSS via runtime `<style>` injection during HMR). `assets` is not\n * required to enumerate every file in dev mode — chunks and styles flow through\n * the dev server on demand.\n */\nexport const Manifest = Schema.Struct({\n // Reuse the build manifest field definitions from `@dxos/protocols` (the shape `composerPlugin`\n // emits), minus `assets` which we relax below, plus the dev-only `devEntry`.\n ...PluginManifestSchema.mapFields((fields) => Struct.omit(fields, ['assets'])).fields,\n /**\n * Relative asset paths. Relaxed vs the build `PluginManifestSchema` (which requires >= 1) because\n * dev-server manifests list no assets — chunks/styles flow through the dev server on demand.\n */\n assets: Schema.Array(Schema.String),\n /** Present only for dev-server manifests; points at the unbundled source entry. */\n devEntry: Schema.optional(Schema.String),\n});\n\nexport type Manifest = Schema.Schema.Type<typeof Manifest>;\n\n/**\n * Resolved manifest with all asset paths converted to absolute URLs.\n *\n * `dev` reflects whether the source manifest declared a `devEntry`. Loaders branch\n * on this to skip offline caching and stylesheet injection — both are no-ops (or\n * actively wrong) when the plugin is being served by a Vite dev server.\n */\nexport type ResolvedManifest = Pick<Manifest, 'key' | 'name' | 'version' | 'dependencies'> & {\n entryUrl: string;\n assetUrls: readonly string[];\n dev: boolean;\n};\n\n/**\n * Resolves a parsed manifest's relative asset paths against the manifest's URL.\n * In dev mode the entry comes from `devEntry`; otherwise it's the canonical\n * {@link PLUGIN_ENTRY_FILENAME} sitting next to the manifest.\n */\nconst resolve = (manifestUrl: string, manifest: Manifest): ResolvedManifest => {\n const dev = manifest.devEntry !== undefined;\n return {\n key: manifest.key,\n name: manifest.name,\n version: manifest.version,\n entryUrl: new URL(dev ? manifest.devEntry! : PLUGIN_ENTRY_FILENAME, manifestUrl).toString(),\n assetUrls: manifest.assets.map((asset) => new URL(asset, manifestUrl).toString()),\n dev,\n ...(manifest.dependencies !== undefined ? { dependencies: manifest.dependencies } : {}),\n };\n};\n\n/**\n * Synchronous decode + validate + resolve for an in-memory manifest payload. Used\n * by tests and tooling that have the manifest body already loaded; the runtime\n * path goes through {@link fetchManifest}.\n */\nexport const parse = (manifestUrl: string, payload: unknown): ResolvedManifest => {\n const manifest = Schema.decodeUnknownSync(Manifest)(payload);\n if (manifest.devEntry === undefined && !manifest.assets.includes(PLUGIN_ENTRY_FILENAME)) {\n throw new Error(`Manifest at ${manifestUrl} does not list ${PLUGIN_ENTRY_FILENAME} in assets.`);\n }\n return resolve(manifestUrl, manifest);\n};\n\n/**\n * Fetches and parses a manifest from the given URL via Effect's HTTP client and\n * Schema-driven JSON decoding. All failure modes surface as a single tagged\n * {@link PluginManifestError}; callers route on `error.context.reason`.\n */\nexport const fetchManifest = (manifestUrl: string): Effect.Effect<ResolvedManifest, PluginManifestError> =>\n Effect.gen(function* () {\n const response = yield* HttpClientRequest.get(manifestUrl).pipe(\n HttpClient.execute,\n Effect.mapError((cause) => new PluginManifestError({ context: { manifestUrl, reason: 'fetch-failed' }, cause })),\n );\n if (response.status >= 400) {\n return yield* Effect.fail(\n new PluginManifestError({ context: { manifestUrl, reason: 'http-error', status: response.status } }),\n );\n }\n const manifest = yield* HttpClientResponse.schemaBodyJson(Manifest)(response).pipe(\n Effect.mapError((cause) => new PluginManifestError({ context: { manifestUrl, reason: 'parse-failed' }, cause })),\n );\n if (manifest.devEntry === undefined && !manifest.assets.includes(PLUGIN_ENTRY_FILENAME)) {\n return yield* Effect.fail(\n new PluginManifestError({\n context: { manifestUrl, reason: 'invalid' },\n cause: `assets does not include ${PLUGIN_ENTRY_FILENAME}`,\n }),\n );\n }\n return resolve(manifestUrl, manifest);\n }).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer));\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,kBAAkB;;;;;;;AAQ/B,IAAa,sBAAb,cAAyC,UAAU,OAAO,uBAAuB,4BAA4B,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;AAiBhH,IAAa,WAAW,OAAO,OAAO;CAGpC,GAAG,qBAAqB,WAAW,WAAW,OAAO,KAAK,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;;;;;CAK/E,QAAQ,OAAO,MAAM,OAAO,MAAM;;CAElC,UAAU,OAAO,SAAS,OAAO,MAAM;AACzC,CAAC;;;;;;AAsBD,IAAM,WAAW,aAAqB,aAAyC;CAC7E,MAAM,MAAM,SAAS,aAAa,KAAA;CAClC,OAAO;EACL,KAAK,SAAS;EACd,MAAM,SAAS;EACf,SAAS,SAAS;EAClB,UAAU,IAAI,IAAI,MAAM,SAAS,WAAY,uBAAuB,WAAW,CAAC,CAAC,SAAS;EAC1F,WAAW,SAAS,OAAO,KAAK,UAAU,IAAI,IAAI,OAAO,WAAW,CAAC,CAAC,SAAS,CAAC;EAChF;EACA,GAAI,SAAS,iBAAiB,KAAA,IAAY,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;CACvF;AACF;;;;;;AAOA,IAAa,SAAS,aAAqB,YAAuC;CAChF,MAAM,WAAW,OAAO,kBAAkB,QAAQ,CAAC,CAAC,OAAO;CAC3D,IAAI,SAAS,aAAa,KAAA,KAAa,CAAC,SAAS,OAAO,SAAS,qBAAqB,GACpF,MAAM,IAAI,MAAM,eAAe,YAAY,iBAAiB,sBAAsB,YAAY;CAEhG,OAAO,QAAQ,aAAa,QAAQ;AACtC;;;;;;AAOA,IAAa,iBAAiB,gBAC5B,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO,kBAAkB,IAAI,WAAW,CAAC,CAAC,KACzD,WAAW,SACX,OAAO,UAAU,UAAU,IAAI,oBAAoB;EAAE,SAAS;GAAE;GAAa,QAAQ;EAAe;EAAG;CAAM,CAAC,CAAC,CACjH;CACA,IAAI,SAAS,UAAU,KACrB,OAAO,OAAO,OAAO,KACnB,IAAI,oBAAoB,EAAE,SAAS;EAAE;EAAa,QAAQ;EAAc,QAAQ,SAAS;CAAO,EAAE,CAAC,CACrG;CAEF,MAAM,WAAW,OAAO,mBAAmB,eAAe,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,KAC5E,OAAO,UAAU,UAAU,IAAI,oBAAoB;EAAE,SAAS;GAAE;GAAa,QAAQ;EAAe;EAAG;CAAM,CAAC,CAAC,CACjH;CACA,IAAI,SAAS,aAAa,KAAA,KAAa,CAAC,SAAS,OAAO,SAAS,qBAAqB,GACpF,OAAO,OAAO,OAAO,KACnB,IAAI,oBAAoB;EACtB,SAAS;GAAE;GAAa,QAAQ;EAAU;EAC1C,OAAO,2BAA2B;CACpC,CAAC,CACH;CAEF,OAAO,QAAQ,aAAa,QAAQ;AACtC,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,KAAK,CAAC"}
|