@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
|
+
import { A as ProvidesMismatchError, D as DuplicateProviderError, E as DependencyCycleError, O as MissingProviderError, k as ModuleActivationError } from "./chunk-capability.mjs";
|
|
3
|
+
import { a as PluginTimeoutError, i as PluginInitializationError, n as isManager, r as make, t as ManagerTypeId } from "./chunk-plugin-manager.mjs";
|
|
4
|
+
import { t as PluginManagerContext } from "./chunk-context.mjs";
|
|
5
|
+
//#region src/core/plugin-manager/index.ts
|
|
6
|
+
var plugin_manager_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
Context: () => PluginManagerContext,
|
|
8
|
+
DependencyCycleError: () => DependencyCycleError,
|
|
9
|
+
DuplicateProviderError: () => DuplicateProviderError,
|
|
10
|
+
ManagerTypeId: () => ManagerTypeId,
|
|
11
|
+
MissingProviderError: () => MissingProviderError,
|
|
12
|
+
ModuleActivationError: () => ModuleActivationError,
|
|
13
|
+
PluginInitializationError: () => PluginInitializationError,
|
|
14
|
+
PluginTimeoutError: () => PluginTimeoutError,
|
|
15
|
+
ProvidesMismatchError: () => ProvidesMismatchError,
|
|
16
|
+
isManager: () => isManager,
|
|
17
|
+
make: () => make
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
export { plugin_manager_exports as t };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=chunk-plugin-manager2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-plugin-manager2.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":""}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import { BaseError } from "@dxos/errors";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import { PLUGIN_ENTRY_FILENAME, PluginManifestSchema } from "@dxos/protocols";
|
|
6
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
7
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
8
|
+
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
9
|
+
import * as HttpClientResponse from "effect/http/HttpClientResponse";
|
|
10
|
+
import * as Struct from "effect/Struct";
|
|
11
|
+
//#region src/core/plugin-manifest.ts
|
|
12
|
+
var plugin_manifest_exports = /* @__PURE__ */ __exportAll({
|
|
13
|
+
DEV_SERVER_PORT: () => DEV_SERVER_PORT,
|
|
14
|
+
Manifest: () => Manifest,
|
|
15
|
+
PluginManifestError: () => PluginManifestError,
|
|
16
|
+
fetchManifest: () => fetchManifest,
|
|
17
|
+
parse: () => parse
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Default port the Vite plugin (`composerPlugin`) binds the dev server to.
|
|
21
|
+
*
|
|
22
|
+
* Shared single source of truth — `composerPlugin` reads it as the default
|
|
23
|
+
* port, and the host's "Load Dev Plugin" affordance pre-fills the manifest URL
|
|
24
|
+
* with `http://localhost:${DEV_SERVER_PORT}/manifest.json`. Lives in
|
|
25
|
+
* app-framework rather than `@dxos/protocols` because the constant is a
|
|
26
|
+
* client-side convention (host loader + Vite plugin) rather than a wire-level
|
|
27
|
+
* protocol.
|
|
28
|
+
*/
|
|
29
|
+
var DEV_SERVER_PORT = 3967;
|
|
30
|
+
/**
|
|
31
|
+
* Tagged error for manifest fetch / parse failures. Construction sites set
|
|
32
|
+
* `context.manifestUrl` and `context.reason` (one of `'fetch-failed' |
|
|
33
|
+
* 'http-error' | 'parse-failed' | 'invalid'`) so handlers can route on the
|
|
34
|
+
* specific failure mode.
|
|
35
|
+
*/
|
|
36
|
+
var PluginManifestError = class extends BaseError.extend("PluginManifestError", "Plugin manifest is invalid") {};
|
|
37
|
+
/**
|
|
38
|
+
* Schema for a third-party plugin manifest.
|
|
39
|
+
*
|
|
40
|
+
* Production manifests are published as a sibling of the plugin's entry module
|
|
41
|
+
* ({@link PLUGIN_ENTRY_FILENAME}) and advertise every file the plugin needs at
|
|
42
|
+
* runtime so the host can eagerly cache them for offline use.
|
|
43
|
+
*
|
|
44
|
+
* Dev manifests (served by `vite dev` via {@link composerPlugin}) set `devEntry`
|
|
45
|
+
* to point at the unbundled source entry. The host's loader treats the presence
|
|
46
|
+
* of `devEntry` as the dev-mode signal: it imports the entry directly from the
|
|
47
|
+
* dev server, skips eager asset caching, and skips static stylesheet injection
|
|
48
|
+
* (Vite handles CSS via runtime `<style>` injection during HMR). `assets` is not
|
|
49
|
+
* required to enumerate every file in dev mode — chunks and styles flow through
|
|
50
|
+
* the dev server on demand.
|
|
51
|
+
*/
|
|
52
|
+
var Manifest = Schema.Struct({
|
|
53
|
+
...PluginManifestSchema.mapFields((fields) => Struct.omit(fields, ["assets"])).fields,
|
|
54
|
+
/**
|
|
55
|
+
* Relative asset paths. Relaxed vs the build `PluginManifestSchema` (which requires >= 1) because
|
|
56
|
+
* dev-server manifests list no assets — chunks/styles flow through the dev server on demand.
|
|
57
|
+
*/
|
|
58
|
+
assets: Schema.Array(Schema.String),
|
|
59
|
+
/** Present only for dev-server manifests; points at the unbundled source entry. */
|
|
60
|
+
devEntry: Schema.optional(Schema.String)
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Resolves a parsed manifest's relative asset paths against the manifest's URL.
|
|
64
|
+
* In dev mode the entry comes from `devEntry`; otherwise it's the canonical
|
|
65
|
+
* {@link PLUGIN_ENTRY_FILENAME} sitting next to the manifest.
|
|
66
|
+
*/
|
|
67
|
+
var resolve = (manifestUrl, manifest) => {
|
|
68
|
+
const dev = manifest.devEntry !== void 0;
|
|
69
|
+
return {
|
|
70
|
+
key: manifest.key,
|
|
71
|
+
name: manifest.name,
|
|
72
|
+
version: manifest.version,
|
|
73
|
+
entryUrl: new URL(dev ? manifest.devEntry : PLUGIN_ENTRY_FILENAME, manifestUrl).toString(),
|
|
74
|
+
assetUrls: manifest.assets.map((asset) => new URL(asset, manifestUrl).toString()),
|
|
75
|
+
dev,
|
|
76
|
+
...manifest.dependencies !== void 0 ? { dependencies: manifest.dependencies } : {}
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Synchronous decode + validate + resolve for an in-memory manifest payload. Used
|
|
81
|
+
* by tests and tooling that have the manifest body already loaded; the runtime
|
|
82
|
+
* path goes through {@link fetchManifest}.
|
|
83
|
+
*/
|
|
84
|
+
var parse = (manifestUrl, payload) => {
|
|
85
|
+
const manifest = Schema.decodeUnknownSync(Manifest)(payload);
|
|
86
|
+
if (manifest.devEntry === void 0 && !manifest.assets.includes(PLUGIN_ENTRY_FILENAME)) throw new Error(`Manifest at ${manifestUrl} does not list ${PLUGIN_ENTRY_FILENAME} in assets.`);
|
|
87
|
+
return resolve(manifestUrl, manifest);
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Fetches and parses a manifest from the given URL via Effect's HTTP client and
|
|
91
|
+
* Schema-driven JSON decoding. All failure modes surface as a single tagged
|
|
92
|
+
* {@link PluginManifestError}; callers route on `error.context.reason`.
|
|
93
|
+
*/
|
|
94
|
+
var fetchManifest = (manifestUrl) => Effect.gen(function* () {
|
|
95
|
+
const response = yield* HttpClientRequest.get(manifestUrl).pipe(HttpClient.execute, Effect.mapError((cause) => new PluginManifestError({
|
|
96
|
+
context: {
|
|
97
|
+
manifestUrl,
|
|
98
|
+
reason: "fetch-failed"
|
|
99
|
+
},
|
|
100
|
+
cause
|
|
101
|
+
})));
|
|
102
|
+
if (response.status >= 400) return yield* Effect.fail(new PluginManifestError({ context: {
|
|
103
|
+
manifestUrl,
|
|
104
|
+
reason: "http-error",
|
|
105
|
+
status: response.status
|
|
106
|
+
} }));
|
|
107
|
+
const manifest = yield* HttpClientResponse.schemaBodyJson(Manifest)(response).pipe(Effect.mapError((cause) => new PluginManifestError({
|
|
108
|
+
context: {
|
|
109
|
+
manifestUrl,
|
|
110
|
+
reason: "parse-failed"
|
|
111
|
+
},
|
|
112
|
+
cause
|
|
113
|
+
})));
|
|
114
|
+
if (manifest.devEntry === void 0 && !manifest.assets.includes(PLUGIN_ENTRY_FILENAME)) return yield* Effect.fail(new PluginManifestError({
|
|
115
|
+
context: {
|
|
116
|
+
manifestUrl,
|
|
117
|
+
reason: "invalid"
|
|
118
|
+
},
|
|
119
|
+
cause: `assets does not include ${PLUGIN_ENTRY_FILENAME}`
|
|
120
|
+
}));
|
|
121
|
+
return resolve(manifestUrl, manifest);
|
|
122
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer));
|
|
123
|
+
//#endregion
|
|
124
|
+
export { parse as a, fetchManifest as i, Manifest as n, plugin_manifest_exports as o, PluginManifestError as r, DEV_SERVER_PORT as t };
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=chunk-plugin-manifest.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-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"}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
|
+
import { t as Idle } from "./chunk-activation-event.mjs";
|
|
3
|
+
import { h as getModuleTag, i as ModuleTag } from "./chunk-capability.mjs";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import { DXN } from "@dxos/keys";
|
|
7
|
+
import * as Context from "effect/Context";
|
|
8
|
+
import { BaseError } from "@dxos/errors";
|
|
9
|
+
import * as Pipeable from "effect/Pipeable";
|
|
10
|
+
import * as Schema from "effect/Schema";
|
|
11
|
+
import { invariant } from "@dxos/invariant";
|
|
12
|
+
import { PluginProfileSchema, PluginReleaseSchema } from "@dxos/protocols";
|
|
13
|
+
//#region src/core/plugin.ts
|
|
14
|
+
var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
15
|
+
LazyPluginError: () => LazyPluginError,
|
|
16
|
+
Meta: () => Meta,
|
|
17
|
+
PluginDependencyError: () => PluginDependencyError,
|
|
18
|
+
PluginModuleTypeId: () => PluginModuleTypeId,
|
|
19
|
+
PluginTypeId: () => PluginTypeId,
|
|
20
|
+
Profile: () => Profile,
|
|
21
|
+
Release: () => Release,
|
|
22
|
+
Service: () => Service,
|
|
23
|
+
activate: () => activate,
|
|
24
|
+
addModule: () => addModule,
|
|
25
|
+
define: () => define,
|
|
26
|
+
getMetaFromConfig: () => getMetaFromConfig,
|
|
27
|
+
getURI: () => getURI,
|
|
28
|
+
isLazy: () => isLazy,
|
|
29
|
+
isPlugin: () => isPlugin,
|
|
30
|
+
isPluginModule: () => isPluginModule,
|
|
31
|
+
lazy: () => lazy,
|
|
32
|
+
make: () => make,
|
|
33
|
+
makeMeta: () => makeMeta,
|
|
34
|
+
reset: () => reset,
|
|
35
|
+
resolveLazy: () => resolveLazy,
|
|
36
|
+
shutdown: () => shutdown
|
|
37
|
+
});
|
|
38
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-framework/src/core/plugin.ts";
|
|
39
|
+
/**
|
|
40
|
+
* Effect Context.Tag for accessing PluginManager via the Effect layer system.
|
|
41
|
+
* This allows lifecycle operations to access the plugin manager without having it passed as an argument.
|
|
42
|
+
*/
|
|
43
|
+
var Service = class extends Context.Service()("@dxos/app-framework/PluginManager") {};
|
|
44
|
+
/**
|
|
45
|
+
* Activates plugins based on the activation event.
|
|
46
|
+
* Accesses the PluginManager via the Effect layer system.
|
|
47
|
+
* @param event The activation event.
|
|
48
|
+
* @returns Whether the activation was successful.
|
|
49
|
+
*/
|
|
50
|
+
var activate = (event) => Effect.flatMap(Service, (manager) => manager.activate(event));
|
|
51
|
+
/**
|
|
52
|
+
* Re-activates the modules that were activated by the event.
|
|
53
|
+
* Accesses the PluginManager via the Effect layer system.
|
|
54
|
+
* @param event The activation event.
|
|
55
|
+
* @returns Whether the reset was successful.
|
|
56
|
+
*/
|
|
57
|
+
var reset = (event) => Effect.flatMap(Service, (manager) => manager.reset(event));
|
|
58
|
+
/**
|
|
59
|
+
* Shuts down the plugin manager, deactivating all active modules and clearing lifecycle state.
|
|
60
|
+
* Accesses the PluginManager via the Effect layer system.
|
|
61
|
+
*/
|
|
62
|
+
var shutdown = () => Effect.flatMap(Service, (manager) => manager.shutdown());
|
|
63
|
+
/**
|
|
64
|
+
* Computes a module ID from plugin ID and export name.
|
|
65
|
+
*/
|
|
66
|
+
var computeModuleId = (pluginId, moduleName) => {
|
|
67
|
+
return `${pluginId}.module.${moduleName}`;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Identifier denoting a PluginModule.
|
|
71
|
+
*/
|
|
72
|
+
var PluginModuleTypeId = Symbol.for("@dxos/app-framework/PluginModule");
|
|
73
|
+
/**
|
|
74
|
+
* Type guard to check if a value is a PluginModule.
|
|
75
|
+
*/
|
|
76
|
+
var isPluginModule = (value) => {
|
|
77
|
+
return typeof value === "object" && value !== null && PluginModuleTypeId in value;
|
|
78
|
+
};
|
|
79
|
+
var PluginModuleImpl = class {
|
|
80
|
+
[PluginModuleTypeId] = PluginModuleTypeId;
|
|
81
|
+
id;
|
|
82
|
+
activation;
|
|
83
|
+
activate;
|
|
84
|
+
constructor(options) {
|
|
85
|
+
this.id = options.id;
|
|
86
|
+
this.activation = options.activation;
|
|
87
|
+
this.activate = options.activate;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Runtime plugin metadata, derived from the `@dxos/protocols` registry schemas (protocols owns the
|
|
92
|
+
* config + registry schemas; the runtime projection lives here).
|
|
93
|
+
*
|
|
94
|
+
* - {@link Profile}: version-independent identity + display + provenance — the wire `PluginProfileSchema`
|
|
95
|
+
* (which carries `key`, the display fields, `dependsOn`, `spec`) plus `author` (resolved at runtime
|
|
96
|
+
* from the publisher's handle/DID for registry plugins, or self-declared for bundled).
|
|
97
|
+
* - {@link Release}: a specific hosted release — the wire `PluginReleaseSchema` (version, moduleUrl, dependencies).
|
|
98
|
+
* - {@link Meta}: a resolved single-release view — `{ profile, release? }`, both nested. `release` is
|
|
99
|
+
* unset for bundled plugins (compiled in) and set for registry plugins.
|
|
100
|
+
*/
|
|
101
|
+
var Profile = Schema.Struct({
|
|
102
|
+
...PluginProfileSchema.fields,
|
|
103
|
+
author: Schema.optional(Schema.String)
|
|
104
|
+
});
|
|
105
|
+
var Release = PluginReleaseSchema;
|
|
106
|
+
var Meta = Schema.Struct({
|
|
107
|
+
profile: Profile,
|
|
108
|
+
release: Schema.optional(Release)
|
|
109
|
+
});
|
|
110
|
+
/** Constructs a bundled-plugin {@link Meta} from a canonical DXN. `profile.key` is the bare NSID derived from `key`. */
|
|
111
|
+
var makeMeta = (options) => {
|
|
112
|
+
const { key, ...rest } = options;
|
|
113
|
+
return { profile: {
|
|
114
|
+
...rest,
|
|
115
|
+
key: DXN.getName(key)
|
|
116
|
+
} };
|
|
117
|
+
};
|
|
118
|
+
/** Returns the plugin's canonical DXN URI, constructed from `meta.profile.key` and `meta.release?.version`. */
|
|
119
|
+
var getURI = (meta) => DXN.make(meta.profile.key, meta.release?.version);
|
|
120
|
+
/**
|
|
121
|
+
* Derives a runtime {@link Meta} from a loaded `dx.config.ts` (the `@dxos/protocols` `Config2.Config`).
|
|
122
|
+
* Maps the flat authoring config to the nested profile; bundled plugins have no `release`.
|
|
123
|
+
*/
|
|
124
|
+
var getMetaFromConfig = ({ plugin }) => {
|
|
125
|
+
const { key, ...rest } = plugin;
|
|
126
|
+
return makeMeta({
|
|
127
|
+
key: DXN.make(key),
|
|
128
|
+
...rest
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Identifier denoting a Plugin.
|
|
133
|
+
*/
|
|
134
|
+
var PluginTypeId = Symbol.for("@dxos/app-framework/Plugin");
|
|
135
|
+
/**
|
|
136
|
+
* Type guard to check if a value is a Plugin.
|
|
137
|
+
*/
|
|
138
|
+
var isPlugin = (value) => {
|
|
139
|
+
return typeof value === "object" && value !== null && PluginTypeId in value;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Internal implementation of Plugin.
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
var PluginImpl = class {
|
|
146
|
+
_meta;
|
|
147
|
+
_modules;
|
|
148
|
+
[PluginTypeId] = PluginTypeId;
|
|
149
|
+
constructor(_meta, _modules) {
|
|
150
|
+
this._meta = _meta;
|
|
151
|
+
this._modules = _modules;
|
|
152
|
+
}
|
|
153
|
+
get meta() {
|
|
154
|
+
return this._meta;
|
|
155
|
+
}
|
|
156
|
+
get modules() {
|
|
157
|
+
return this._modules;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Builder implementation for creating plugins incrementally.
|
|
162
|
+
*/
|
|
163
|
+
var PluginBuilderImpl = class {
|
|
164
|
+
meta;
|
|
165
|
+
_modules = [];
|
|
166
|
+
constructor(meta) {
|
|
167
|
+
this.meta = meta;
|
|
168
|
+
}
|
|
169
|
+
get modules() {
|
|
170
|
+
return this._modules;
|
|
171
|
+
}
|
|
172
|
+
addModule(moduleOptions) {
|
|
173
|
+
this._modules.push(moduleOptions);
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
pipe() {
|
|
177
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Creates a new PluginBuilder to start building a plugin.
|
|
182
|
+
*/
|
|
183
|
+
var define = (meta) => new PluginBuilderImpl(meta);
|
|
184
|
+
function addModule(moduleOrOptionsOrBuilder, moduleOptions) {
|
|
185
|
+
if (moduleOrOptionsOrBuilder === void 0) return (builder) => builder;
|
|
186
|
+
if (typeof moduleOrOptionsOrBuilder === "function" && ModuleTag in moduleOrOptionsOrBuilder) {
|
|
187
|
+
const module = moduleOrOptionsOrBuilder;
|
|
188
|
+
const { id } = moduleOptions ?? {};
|
|
189
|
+
return (builder) => builder.addModule((pluginOptions) => ({
|
|
190
|
+
id: id ?? getModuleTag(module),
|
|
191
|
+
activatesOn: module.activatesOn,
|
|
192
|
+
requires: module.requires,
|
|
193
|
+
provides: module.provides,
|
|
194
|
+
activate: () => module(pluginOptions)
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
if (moduleOptions !== void 0) return moduleOrOptionsOrBuilder.addModule(moduleOptions);
|
|
198
|
+
const moduleOpts = moduleOrOptionsOrBuilder;
|
|
199
|
+
return (builder) => builder.addModule(moduleOpts);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Normalizes an authoring record to an {@link ActivationSpec} — the single boundary where the
|
|
203
|
+
* optional authoring shape becomes the concrete runtime shape.
|
|
204
|
+
*
|
|
205
|
+
* Omitting `activatesOn` means the module belongs to the IDLE wave: a module that must run at
|
|
206
|
+
* boot states `activatesOn: ActivationEvents.Startup` explicitly. Inverted from a startup default
|
|
207
|
+
* so that forgetting the annotation costs post-paint responsiveness rather than time-to-
|
|
208
|
+
* interactive — startup was the dumping ground, and its cost becomes the transitive demand
|
|
209
|
+
* closure of the boot path, enforced by the capability graph instead of by remembering to
|
|
210
|
+
* annotate. Un-annotated modules stay pullable as providers because the baseline wave is
|
|
211
|
+
* `Startup u Idle`; see `ActivationScheduler.#isBaselineWave`, which moves with this default.
|
|
212
|
+
*/
|
|
213
|
+
var normalizeActivation = (options) => ({
|
|
214
|
+
activatesOn: options.activatesOn ?? Idle,
|
|
215
|
+
requires: options.requires ?? [],
|
|
216
|
+
provides: options.provides ?? []
|
|
217
|
+
});
|
|
218
|
+
/**
|
|
219
|
+
* Resolves a module from either a module entry or a function that returns one.
|
|
220
|
+
*/
|
|
221
|
+
var resolveModule = (meta, module, options) => {
|
|
222
|
+
const moduleOptions = typeof module === "function" ? module(options) : module;
|
|
223
|
+
const pluginName = meta.profile.key;
|
|
224
|
+
return new PluginModuleImpl({
|
|
225
|
+
id: Option.fromNullishOr(moduleOptions.id).pipe(Option.match({
|
|
226
|
+
onNone: () => {
|
|
227
|
+
const exportName = getModuleTag(moduleOptions.activate);
|
|
228
|
+
invariant(exportName, `Plugin module missing name. Plugin: ${meta.profile.key}`, {
|
|
229
|
+
"~LogMeta": "~LogMeta",
|
|
230
|
+
F: __dxlog_file,
|
|
231
|
+
L: 485,
|
|
232
|
+
S: void 0,
|
|
233
|
+
A: ["exportName", "`Plugin module missing name. Plugin: ${meta.profile.key}`"]
|
|
234
|
+
});
|
|
235
|
+
return computeModuleId(pluginName, exportName);
|
|
236
|
+
},
|
|
237
|
+
onSome: (id) => computeModuleId(pluginName, id)
|
|
238
|
+
})),
|
|
239
|
+
activation: normalizeActivation(moduleOptions),
|
|
240
|
+
activate: moduleOptions.activate
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
function make(builder) {
|
|
244
|
+
const meta = builder.meta;
|
|
245
|
+
invariant(!meta.profile.dependsOn?.includes(meta.profile.key), `Plugin ${meta.profile.key} declares itself as a dependency.`, {
|
|
246
|
+
"~LogMeta": "~LogMeta",
|
|
247
|
+
F: __dxlog_file,
|
|
248
|
+
L: 513,
|
|
249
|
+
S: this,
|
|
250
|
+
A: ["!meta.profile.dependsOn?.includes(meta.profile.key)", "`Plugin ${meta.profile.key} declares itself as a dependency.`"]
|
|
251
|
+
});
|
|
252
|
+
const factory = (options) => {
|
|
253
|
+
const resolved = options ?? {};
|
|
254
|
+
const modules = builder.modules.map((module) => resolveModule(meta, module, resolved));
|
|
255
|
+
const ids = /* @__PURE__ */ new Set();
|
|
256
|
+
for (const module of modules) {
|
|
257
|
+
invariant(!ids.has(module.id), `Duplicate module id ${module.id} in plugin ${meta.profile.key}.`, {
|
|
258
|
+
"~LogMeta": "~LogMeta",
|
|
259
|
+
F: __dxlog_file,
|
|
260
|
+
L: 526,
|
|
261
|
+
S: this,
|
|
262
|
+
A: ["!ids.has(module.id)", "`Duplicate module id ${module.id} in plugin ${meta.profile.key}.`"]
|
|
263
|
+
});
|
|
264
|
+
ids.add(module.id);
|
|
265
|
+
}
|
|
266
|
+
return new PluginImpl(meta, modules);
|
|
267
|
+
};
|
|
268
|
+
return Object.assign(factory, { meta });
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Symbol used to tag lazy plugin stubs with their loader closure.
|
|
272
|
+
* Hidden from enumeration so plugin manager iteration / serialization paths
|
|
273
|
+
* don't trip over it.
|
|
274
|
+
*/
|
|
275
|
+
var LazyTag = Symbol.for("@dxos/app-framework/Plugin/Lazy");
|
|
276
|
+
function lazy(meta, loader) {
|
|
277
|
+
const factory = (options) => {
|
|
278
|
+
const stub = new PluginImpl(meta, []);
|
|
279
|
+
Object.defineProperty(stub, LazyTag, {
|
|
280
|
+
value: {
|
|
281
|
+
loader,
|
|
282
|
+
options
|
|
283
|
+
},
|
|
284
|
+
enumerable: false
|
|
285
|
+
});
|
|
286
|
+
return stub;
|
|
287
|
+
};
|
|
288
|
+
return Object.assign(factory, { meta });
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Type guard for lazy plugin stubs produced by {@link lazy}.
|
|
292
|
+
*/
|
|
293
|
+
var isLazy = (plugin) => LazyTag in plugin;
|
|
294
|
+
/**
|
|
295
|
+
* Tagged error for failures during lazy plugin resolution. `context.id` is
|
|
296
|
+
* the lazy plugin's `meta.profile.key`; `context.reason` discriminates the failure
|
|
297
|
+
* mode (`'load-failed' | 'missing-default' | 'invalid-plugin' |
|
|
298
|
+
* 'meta-mismatch'`) so callers can route on it.
|
|
299
|
+
*/
|
|
300
|
+
var LazyPluginError = class extends BaseError.extend("LazyPluginError", "Failed to resolve lazy plugin") {};
|
|
301
|
+
/**
|
|
302
|
+
* Tagged error for plugin-level dependency resolution failures.
|
|
303
|
+
*
|
|
304
|
+
* `context.id` is the plugin id the manager was acting on. `context.reason`
|
|
305
|
+
* discriminates the failure mode:
|
|
306
|
+
* - `'missing'` — declared dep is neither registered nor in the catalog.
|
|
307
|
+
* `context.missing` lists offending ids.
|
|
308
|
+
* - `'install-failed'` — dep was found in the catalog but `add()` failed.
|
|
309
|
+
* `cause` carries the original error.
|
|
310
|
+
* - `'cycle'` — closure walk detected a cycle. `context.path` is the cycle path.
|
|
311
|
+
* - `'core-dependent'` — cascade-disable would have to disable a core plugin.
|
|
312
|
+
* `context.coreDependent` is the blocking id.
|
|
313
|
+
*/
|
|
314
|
+
var PluginDependencyError = class extends BaseError.extend("PluginDependencyError", "Plugin dependency resolution failed") {};
|
|
315
|
+
/**
|
|
316
|
+
* Resolves a lazy plugin stub to its real plugin.
|
|
317
|
+
* Returns the plugin unchanged if it is not lazy. Failures surface as
|
|
318
|
+
* {@link LazyPluginError} with `context.reason` indicating the failure mode
|
|
319
|
+
* and (for loader failures) `cause` set to the original error.
|
|
320
|
+
*/
|
|
321
|
+
var resolveLazy = (plugin) => Effect.gen(function* () {
|
|
322
|
+
if (!isLazy(plugin)) return plugin;
|
|
323
|
+
const id = plugin.meta.profile.key;
|
|
324
|
+
const { loader, options } = plugin[LazyTag];
|
|
325
|
+
const mod = yield* Effect.tryPromise({
|
|
326
|
+
try: loader,
|
|
327
|
+
catch: (error) => new LazyPluginError({
|
|
328
|
+
context: {
|
|
329
|
+
id,
|
|
330
|
+
reason: "load-failed"
|
|
331
|
+
},
|
|
332
|
+
cause: error
|
|
333
|
+
})
|
|
334
|
+
});
|
|
335
|
+
if (!mod || typeof mod.default !== "function") return yield* Effect.fail(new LazyPluginError({ context: {
|
|
336
|
+
id,
|
|
337
|
+
reason: "missing-default"
|
|
338
|
+
} }));
|
|
339
|
+
const result = mod.default(options);
|
|
340
|
+
if (!isPlugin(result)) return yield* Effect.fail(new LazyPluginError({ context: {
|
|
341
|
+
id,
|
|
342
|
+
reason: "invalid-plugin"
|
|
343
|
+
} }));
|
|
344
|
+
if (result.meta.profile.key !== id) return yield* Effect.fail(new LazyPluginError({ context: {
|
|
345
|
+
id,
|
|
346
|
+
reason: "meta-mismatch",
|
|
347
|
+
returnedId: result.meta.profile.key
|
|
348
|
+
} }));
|
|
349
|
+
return result;
|
|
350
|
+
});
|
|
351
|
+
//#endregion
|
|
352
|
+
export { shutdown as C, resolveLazy as S, lazy as _, PluginTypeId as a, plugin_exports as b, Service as c, define as d, getMetaFromConfig as f, isPluginModule as g, isPlugin as h, PluginModuleTypeId as i, activate as l, isLazy as m, Meta as n, Profile as o, getURI as p, PluginDependencyError as r, Release as s, LazyPluginError as t, addModule as u, make as v, reset as x, makeMeta as y };
|
|
353
|
+
|
|
354
|
+
//# sourceMappingURL=chunk-plugin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-plugin.mjs","names":["Capability.ModuleTag","Capability.getModuleTag","ActivationEvent.Idle"],"sources":["../../src/core/plugin.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport * as Pipeable from 'effect/Pipeable';\nimport * as Schema from 'effect/Schema';\nimport type * as Scope from 'effect/Scope';\nimport type * as Types from 'effect/Types';\n\nimport { BaseError } from '@dxos/errors';\nimport { invariant } from '@dxos/invariant';\nimport { DXN } from '@dxos/keys';\nimport { Config2, PluginProfileSchema, PluginReleaseSchema } from '@dxos/protocols';\n\nimport * as ActivationEvent from './activation-event.ts';\nimport * as Capability from './capability.ts';\nimport type * as PluginManager from './plugin-manager/index.ts';\n\n//\n// Plugin Service Layer\n//\n\n/**\n * Effect Context.Tag for accessing PluginManager via the Effect layer system.\n * This allows lifecycle operations to access the plugin manager without having it passed as an argument.\n */\nexport class Service extends Context.Service<Service, PluginManager.PluginManager>()(\n '@dxos/app-framework/PluginManager',\n) {}\n\n//\n// Lifecycle Functions\n//\n\n/**\n * Activates plugins based on the activation event.\n * Accesses the PluginManager via the Effect layer system.\n * @param event The activation event.\n * @returns Whether the activation was successful.\n */\nexport const activate = (event: ActivationEvent.ActivationEvent): Effect.Effect<boolean, Error, Service> =>\n Effect.flatMap(Service, (manager) => manager.activate(event));\n\n/**\n * Re-activates the modules that were activated by the event.\n * Accesses the PluginManager via the Effect layer system.\n * @param event The activation event.\n * @returns Whether the reset was successful.\n */\nexport const reset = (event: ActivationEvent.ActivationEvent): Effect.Effect<boolean, Error, Service> =>\n Effect.flatMap(Service, (manager) => manager.reset(event));\n\n/**\n * Shuts down the plugin manager, deactivating all active modules and clearing lifecycle state.\n * Accesses the PluginManager via the Effect layer system.\n */\nexport const shutdown = (): Effect.Effect<boolean, Error, Service> =>\n Effect.flatMap(Service, (manager) => manager.shutdown());\n\n/**\n * Computes a module ID from plugin ID and export name.\n */\nconst computeModuleId = (pluginId: string, moduleName: string): string => {\n return `${pluginId}.module.${moduleName}`;\n};\n\n/**\n * Identifier denoting a PluginModule.\n */\nexport const PluginModuleTypeId: unique symbol = Symbol.for('@dxos/app-framework/PluginModule');\nexport type PluginModuleTypeId = typeof PluginModuleTypeId;\n\n/**\n * Type guard to check if a value is a PluginModule.\n */\nexport const isPluginModule = (value: unknown): value is PluginModule => {\n return typeof value === 'object' && value !== null && PluginModuleTypeId in value;\n};\n\n/**\n * Normalized activation specification of a module — which wave it belongs to and what it\n * exchanges with the capability graph.\n *\n * Every module names a wave: `activatesOn` defaults to {@link ActivationEvent.Idle} when the\n * authoring record omits it. Within a wave, modules are ordered topologically (providers of\n * `requires` activate first) and a module may pull a provider whose own wave has already fired.\n * Because a fired event stays fired, a module gated on an event that has already passed remains\n * eligible in every later round — which is what lets a consumer activate once a provider from\n * some other wave finally lands.\n */\nexport type ActivationSpec = {\n readonly activatesOn: ActivationEvent.Events;\n readonly requires: readonly Capability.AnyTag[];\n readonly provides: readonly Capability.AnyTag[];\n};\n\n/**\n * What a module's activate may produce at runtime: raw capability entries (see\n * {@link Capability.ModuleReturn}) or typed {@link Capability.Contribution}s.\n */\nexport type ModuleActivateResult = Capability.ModuleReturn | readonly Capability.AnyContribution[];\n\n/**\n * A unit of containment of modular functionality that can be provided to an application.\n * Activation of a module is async allowing for code to split and loaded lazily.\n *\n * Ordering between modules is expressed through capability dependencies, not registration\n * order: a module declares the capabilities it `provides` and `requires`, and the plugin\n * manager activates providers before consumers (topological order). Runtime-event modules\n * (`activatesOn`) activate only when their event fires. See\n * `plugin-client/src/ClientPlugin.ts` for a worked example.\n *\n * @idiom org.dxos.app-framework.moduleActivationOrdering\n * applies: sequencing one plugin module's activation before or after another\n * instead-of: assuming module registration order controls activation order\n * uses: {@link ActivationSpec}\n */\nexport interface PluginModule {\n readonly [PluginModuleTypeId]: PluginModuleTypeId;\n /**\n * Unique id of the module.\n */\n id: string;\n\n /**\n * Normalized activation specification — how and when the module activates.\n */\n readonly activation: ActivationSpec;\n\n /**\n * Called when the module is activated.\n * Declared `requires` are provided as Effect services; CapabilityManager and PluginManager\n * are ambient (Capability.Service, Plugin.Service).\n * @param props Optional props passed to the module.\n * @returns The capabilities of the module.\n */\n activate: (\n props?: any,\n ) => Effect.Effect<ModuleActivateResult, Error, Capability.Service | Service | Scope.Scope | never>;\n}\n\n/**\n * Structural authoring shape for typed modules. A module without `requires` is a root,\n * triggered by `activatesOn` (implicitly Idle). A module with `requires` is a chain\n * member: it activates once every declared capability has been contributed — by whichever\n * event's chain produced the providers, not necessarily during startup — additionally gated\n * on `activatesOn` when declared. Consumers of event-gated providers stay pending until the\n * event fires and the provider contributes; capability cycles fail at runtime\n * (DependencyCycleError). Channel enforcement happens in {@link ValidateModuleOptions}.\n */\nexport type TypedModuleOptions = {\n readonly id?: string;\n readonly activatesOn?: ActivationEvent.Events;\n readonly requires?: readonly Capability.AnyTag[];\n readonly provides?: readonly Capability.AnyTag[];\n readonly activate: (props?: any) => Effect.Effect<any, any, any>;\n};\n\ntype OptRequires<Opts> = Opts extends { requires: infer R extends readonly Capability.AnyTag[] } ? R : readonly [];\ntype OptProvides<Opts> = Opts extends { provides: infer P extends readonly Capability.AnyTag[] } ? P : readonly [];\n\n/**\n * Validates typed module options: evaluates to `true` for a valid module, otherwise to an\n * unconstructible branded type naming the violation. Checks that activate's environment only\n * uses declared `requires`, that its error channel extends `Error`, and that its return\n * exactly covers the declared `provides`. Options with no capability declarations are a\n * dependency-mode root and validate as `true` (the runtime normalizer classifies them).\n *\n * Applied on {@link addModule}'s return type (not intersected with the parameter): inference\n * of `Opts` from an options object containing inline generator activates fails when the\n * parameter is an intersection with a conditional over `Opts`, but succeeds for a naked\n * `Opts` parameter. An invalid module surfaces as a non-callable branded return, which the\n * `.pipe(...)` site rejects with the brand's message.\n */\nexport type ValidateModuleOptions<Opts extends TypedModuleOptions> = Opts extends\n | { provides: readonly Capability.AnyTag[] }\n | { requires: readonly Capability.AnyTag[] }\n | { activatesOn: ActivationEvent.Events; requires: readonly Capability.AnyTag[] }\n ? Opts['activate'] extends (props?: any) => Effect.Effect<infer A, infer E, infer R>\n ? [R] extends [Capability.Requirements<OptRequires<Opts>>]\n ? [E] extends [Error]\n ? [A] extends [Capability.ProvidesReturn<OptProvides<Opts>>]\n ? [Capability.ProvidedIds<OptProvides<Opts>>] extends [Capability.CoveredBy<A>]\n ? true\n : {\n readonly 'activate return misses declared capabilities': Exclude<\n Capability.ProvidedIds<OptProvides<Opts>>,\n Capability.CoveredBy<A>\n >;\n }\n : {\n readonly 'activate return includes undeclared capabilities': Exclude<\n Capability.CoveredBy<A>,\n Capability.ProvidedIds<OptProvides<Opts>>\n >;\n }\n : { readonly 'activate error channel must extend Error': E }\n : { readonly 'activate requires undeclared capabilities': Exclude<R, Capability.Requirements<OptRequires<Opts>>> }\n : { readonly 'activate must be an effect-returning function': Opts['activate'] }\n : true;\n\n/**\n * Erased module authoring record stored on the builder. Type enforcement happens at the\n * {@link addModule} overloads; the builder itself is intentionally heterogeneous.\n */\nexport type ModuleEntry = {\n readonly id?: string;\n readonly activatesOn?: ActivationEvent.Events;\n readonly requires?: readonly Capability.AnyTag[];\n readonly provides?: readonly Capability.AnyTag[];\n readonly activate: (props?: any) => Effect.Effect<any, Error, any>;\n};\n\nclass PluginModuleImpl implements PluginModule {\n readonly [PluginModuleTypeId]: PluginModuleTypeId = PluginModuleTypeId;\n readonly id: string;\n readonly activation: ActivationSpec;\n readonly activate: PluginModule['activate'];\n\n constructor(options: { id: string; activation: ActivationSpec; activate: PluginModule['activate'] }) {\n this.id = options.id;\n this.activation = options.activation;\n this.activate = options.activate;\n }\n}\n\n/**\n * Runtime plugin metadata, derived from the `@dxos/protocols` registry schemas (protocols owns the\n * config + registry schemas; the runtime projection lives here).\n *\n * - {@link Profile}: version-independent identity + display + provenance — the wire `PluginProfileSchema`\n * (which carries `key`, the display fields, `dependsOn`, `spec`) plus `author` (resolved at runtime\n * from the publisher's handle/DID for registry plugins, or self-declared for bundled).\n * - {@link Release}: a specific hosted release — the wire `PluginReleaseSchema` (version, moduleUrl, dependencies).\n * - {@link Meta}: a resolved single-release view — `{ profile, release? }`, both nested. `release` is\n * unset for bundled plugins (compiled in) and set for registry plugins.\n */\nexport const Profile = Schema.Struct({\n ...PluginProfileSchema.fields,\n author: Schema.optional(Schema.String),\n});\nexport type Profile = Schema.Schema.Type<typeof Profile>;\n\nexport const Release = PluginReleaseSchema;\nexport type Release = Schema.Schema.Type<typeof Release>;\n\nexport const Meta = Schema.Struct({\n profile: Profile,\n release: Schema.optional(Release),\n});\nexport type Meta = Schema.Schema.Type<typeof Meta>;\n\n/** Options for {@link makeMeta}: the {@link Profile} content fields plus the canonical DXN `key`. */\nexport type MakeMetaOptions = {\n key: DXN.DXN;\n name: string;\n description?: string;\n author?: string;\n homePage?: string;\n source?: string;\n tags?: readonly string[];\n dependsOn?: readonly string[];\n spec?: string;\n icon?: Config2.Icon;\n screenshots?: ReadonlyArray<Config2.Screenshot>;\n};\n\n/** Constructs a bundled-plugin {@link Meta} from a canonical DXN. `profile.key` is the bare NSID derived from `key`. */\nexport const makeMeta = (options: MakeMetaOptions): Meta => {\n const { key, ...rest } = options;\n return { profile: { ...rest, key: DXN.getName(key) } };\n};\n\n/** Returns the plugin's canonical DXN URI, constructed from `meta.profile.key` and `meta.release?.version`. */\nexport const getURI = (meta: Meta): DXN.DXN => DXN.make(meta.profile.key, meta.release?.version);\n\n/**\n * Derives a runtime {@link Meta} from a loaded `dx.config.ts` (the `@dxos/protocols` `Config2.Config`).\n * Maps the flat authoring config to the nested profile; bundled plugins have no `release`.\n */\nexport const getMetaFromConfig = ({ plugin }: Config2.Config): Meta => {\n const { key, ...rest } = plugin;\n return makeMeta({ key: DXN.make(key), ...rest });\n};\n\n/**\n * Identifier denoting a Plugin.\n */\nexport const PluginTypeId: unique symbol = Symbol.for('@dxos/app-framework/Plugin');\nexport type PluginTypeId = typeof PluginTypeId;\n\n/**\n * Type guard to check if a value is a Plugin.\n */\nexport const isPlugin = (value: unknown): value is Plugin => {\n return typeof value === 'object' && value !== null && PluginTypeId in value;\n};\n\n/**\n * A collection of modules that are be enabled/disabled as a unit.\n * Plugins provide things such as components, state, actions, etc. to the application.\n */\n// TODO(burdon): Convert to ECHO schema.\nexport interface Plugin {\n readonly [PluginTypeId]: PluginTypeId;\n readonly meta: Readonly<Meta>;\n readonly modules: ReadonlyArray<PluginModule>;\n}\n\n/**\n * Internal implementation of Plugin.\n * @internal\n */\nclass PluginImpl implements Plugin {\n readonly [PluginTypeId]: PluginTypeId = PluginTypeId;\n\n constructor(\n private readonly _meta: Meta,\n private readonly _modules: PluginModule[],\n ) {}\n\n get meta(): Readonly<Meta> {\n return this._meta;\n }\n\n get modules(): ReadonlyArray<PluginModule> {\n return this._modules;\n }\n}\n\n/**\n * Builder interface for creating plugins incrementally.\n */\nexport interface PluginBuilder<T = void> extends Pipeable.Pipeable {\n readonly meta: Meta;\n readonly modules: ReadonlyArray<ModuleEntry | ((options: T) => ModuleEntry)>;\n addModule(moduleOptions: ModuleEntry | ((options: T) => ModuleEntry)): PluginBuilder<T>;\n}\n\n/**\n * Builder implementation for creating plugins incrementally.\n */\nclass PluginBuilderImpl<T = void> implements PluginBuilder<T> {\n readonly meta: Meta;\n private readonly _modules: Array<ModuleEntry | ((options: T) => ModuleEntry)> = [];\n\n constructor(meta: Meta) {\n this.meta = meta;\n }\n\n get modules(): ReadonlyArray<ModuleEntry | ((options: T) => ModuleEntry)> {\n return this._modules;\n }\n\n addModule(moduleOptions: ModuleEntry | ((options: T) => ModuleEntry)): PluginBuilder<T> {\n this._modules.push(moduleOptions);\n return this;\n }\n\n pipe() {\n // eslint-disable-next-line prefer-rest-params\n return Pipeable.pipeArguments(this, arguments);\n }\n}\n\n/**\n * Creates a new PluginBuilder to start building a plugin.\n */\nexport const define = <T = void>(meta: Meta): PluginBuilder<T> => new PluginBuilderImpl<T>(meta);\n\n/**\n * Adds a module to a plugin builder. Supports both pipeline and direct call styles.\n *\n * Accepts either:\n * - A spec-carrying module ({@link Capability.lazyModule} / {@link Capability.inlineModule}):\n * requires/provides/activatesOn/props all come from the module's own spec (declared where\n * it is authored), so the call needs only an optional id override.\n * - An inline authoring record (or a function receiving plugin options): declares\n * `provides`/`requires` (dependency mode) or `activatesOn` + `requires`/`provides`\n * (runtime-event mode); the activate effect's environment is constrained to the declared\n * requires and its return must cover the declared provides.\n */\nexport function addModule<T = void>(\n module: Capability.Module<void> | undefined,\n options?: { id?: string },\n): (builder: PluginBuilder<T>) => PluginBuilder<T>;\n/** Spec-carrying module whose props are mapped from the plugin options. */\nexport function addModule<Options, T extends Options = Options>(\n module: Capability.Module<Options>,\n options?: { id?: string },\n): (builder: PluginBuilder<T>) => PluginBuilder<T>;\nexport function addModule<T, const Opts extends Types.NoExcessProperties<TypedModuleOptions, Opts>>(\n moduleOptions: Opts | ((options: T) => Opts),\n): true extends ValidateModuleOptions<Opts>\n ? (builder: PluginBuilder<T>) => PluginBuilder<T>\n : ValidateModuleOptions<Opts>;\n/** Direct-call variant of the typed record overload above (see {@link PluginBuilder.addModule}). */\nexport function addModule<T>(\n builder: PluginBuilder<T>,\n moduleOptions: TypedModuleOptions | ((options: T) => TypedModuleOptions),\n): PluginBuilder<T>;\nexport function addModule<T>(\n moduleOrOptionsOrBuilder:\n | Capability.Module<any>\n | ModuleEntry\n | ((options: T) => ModuleEntry)\n | PluginBuilder<T>\n | undefined,\n moduleOptions?: ModuleEntry | ((options: T) => ModuleEntry) | { id?: string },\n): ((builder: PluginBuilder<T>) => PluginBuilder<T>) | PluginBuilder<T> {\n // Headless capability barrels stub excluded modules as `undefined`, letting one canonical\n // plugin entry serve every environment.\n if (moduleOrOptionsOrBuilder === undefined) {\n return (builder: PluginBuilder<T>) => builder;\n }\n // Spec-carrying module: a tagged function whose requires/provides/activatesOn come from its\n // own spec; only an optional id override is supplied at the call site.\n if (typeof moduleOrOptionsOrBuilder === 'function' && Capability.ModuleTag in moduleOrOptionsOrBuilder) {\n const module = moduleOrOptionsOrBuilder as Capability.Module<any>;\n const { id } = (moduleOptions as { id?: string } | undefined) ?? {};\n return (builder: PluginBuilder<T>) =>\n builder.addModule((pluginOptions: T) => ({\n id: id ?? Capability.getModuleTag(module),\n activatesOn: module.activatesOn,\n requires: module.requires,\n provides: module.provides,\n activate: () => module(pluginOptions),\n }));\n }\n // Direct-call style: addModule(builder, moduleOptions).\n if (moduleOptions !== undefined) {\n return (moduleOrOptionsOrBuilder as PluginBuilder<T>).addModule(\n moduleOptions as ModuleEntry | ((options: T) => ModuleEntry),\n );\n }\n // Pipeline style: addModule(moduleOptions) returns a function.\n const moduleOpts = moduleOrOptionsOrBuilder as ModuleEntry | ((options: T) => ModuleEntry);\n return (builder: PluginBuilder<T>) => builder.addModule(moduleOpts);\n}\n\n/**\n * A plugin factory. The options parameter is optional exactly when `T` has no required members\n * (`{} extends T` — every field is optional, or `T` is `void`), so `SomePlugin()` is accepted\n * without an empty `{}`; a plugin whose options include a required field still demands them.\n */\nexport type PluginFactory<T = void> = ({} extends T ? (options?: T) => Plugin : (options: T) => Plugin) & {\n meta: Meta;\n};\n\n/**\n * Normalizes an authoring record to an {@link ActivationSpec} — the single boundary where the\n * optional authoring shape becomes the concrete runtime shape.\n *\n * Omitting `activatesOn` means the module belongs to the IDLE wave: a module that must run at\n * boot states `activatesOn: ActivationEvents.Startup` explicitly. Inverted from a startup default\n * so that forgetting the annotation costs post-paint responsiveness rather than time-to-\n * interactive — startup was the dumping ground, and its cost becomes the transitive demand\n * closure of the boot path, enforced by the capability graph instead of by remembering to\n * annotate. Un-annotated modules stay pullable as providers because the baseline wave is\n * `Startup u Idle`; see `ActivationScheduler.#isBaselineWave`, which moves with this default.\n */\nconst normalizeActivation = (options: ModuleEntry): ActivationSpec => ({\n activatesOn: options.activatesOn ?? ActivationEvent.Idle,\n requires: options.requires ?? [],\n provides: options.provides ?? [],\n});\n\n/**\n * Resolves a module from either a module entry or a function that returns one.\n */\nconst resolveModule = (\n meta: Meta,\n module: ModuleEntry | ((options: any) => ModuleEntry),\n options?: any,\n): PluginModuleImpl => {\n const moduleOptions = typeof module === 'function' ? module(options) : module;\n const pluginName = meta.profile.key;\n const id = Option.fromNullishOr(moduleOptions.id).pipe(\n Option.match({\n onNone: () => {\n const exportName = Capability.getModuleTag(moduleOptions.activate);\n invariant(exportName, `Plugin module missing name. Plugin: ${meta.profile.key}`);\n return computeModuleId(pluginName, exportName);\n },\n onSome: (id) => computeModuleId(pluginName, id),\n }),\n );\n return new PluginModuleImpl({\n id,\n activation: normalizeActivation(moduleOptions),\n // Erasure boundary: the authoring layer constrained the effect's environment to the\n // declared requires; the manager supplies exactly those services plus the ambient ones.\n activate: moduleOptions.activate as PluginModule['activate'],\n });\n};\n\n/**\n * Creates a Plugin from a builder.\n * Supports both pipeline and direct call styles.\n * Always returns a factory function (options: T) => Plugin.\n * When T is void, the function takes no arguments: () => Plugin.\n */\nexport function make<T>(builder: PluginBuilder<T>): PluginFactory<T>;\n// The impl return names the required-parameter form; the public overload above narrows it to the\n// optional-parameter form when `T` has no required members (the conditional can't be reduced for a\n// generic `T` inside the body, so it is stated on the overload instead).\nexport function make<T>(builder: PluginBuilder<T>): ((options: T) => Plugin) & { meta: Meta } {\n const meta = builder.meta;\n // `dependsOn` entries and `key` are both bare NSIDs, so compare directly.\n invariant(\n !meta.profile.dependsOn?.includes(meta.profile.key),\n `Plugin ${meta.profile.key} declares itself as a dependency.`,\n );\n\n const factory = (options?: T) => {\n // Default to `{}` so an omitted all-optional options object (see {@link PluginFactory}) is\n // safe for option-reading module callbacks; ignored by `void` and record modules.\n const resolved = (options ?? {}) as T;\n const modules = builder.modules.map((module) => resolveModule(meta, module, resolved));\n // The manager keys modules by id, so a second module with the same id would be dropped silently.\n const ids = new Set<string>();\n for (const module of modules) {\n invariant(!ids.has(module.id), `Duplicate module id ${module.id} in plugin ${meta.profile.key}.`);\n ids.add(module.id);\n }\n return new PluginImpl(meta, modules);\n };\n\n return Object.assign(factory, { meta });\n}\n\n//\n// Lazy plugin loading\n//\n\n/**\n * Symbol used to tag lazy plugin stubs with their loader closure.\n * Hidden from enumeration so plugin manager iteration / serialization paths\n * don't trip over it.\n */\nconst LazyTag: unique symbol = Symbol.for('@dxos/app-framework/Plugin/Lazy');\n\n/**\n * Async loader for a lazy plugin's real implementation.\n * The default export of the loaded module must be a `PluginFactory<T>` —\n * i.e. the same shape `Plugin.make` produces.\n */\nexport type LazyLoader<T = void> = () => Promise<{ default: PluginFactory<T> }>;\n\n/** Internal: payload carried on a lazy stub. */\ntype LazyPayload = { loader: LazyLoader<any>; options: unknown };\n\n/**\n * Defines a lazy plugin whose body is loaded on first enable.\n *\n * The returned factory produces a stub `Plugin` that exposes `meta`\n * synchronously (so callers can read `Plugin.meta.profile.key` for free) but defers\n * loading the real plugin's modules until the manager calls\n * `Plugin.resolveLazy`. This lets the plugin's main entry point ship as a\n * tiny meta-only chunk — the heavy capabilities, schema, React surfaces,\n * etc. live behind the dynamic `import()` and become a separate Rollup\n * chunk that is only fetched when the plugin is enabled.\n *\n * @example\n * ```ts\n * // plugin-markdown/src/index.ts\n * import * as Plugin from '@dxos/app-framework/Plugin';\n * import { meta } from './meta';\n *\n * export const MarkdownPlugin = Plugin.lazy(meta, () => import('./MarkdownPlugin'));\n *\n * // plugin-markdown/src/MarkdownPlugin.tsx\n * export const MarkdownPlugin = Plugin.define(meta).pipe(...heavy modules..., Plugin.make);\n * export default MarkdownPlugin;\n * ```\n */\nexport function lazy<T = void>(meta: Meta, loader: LazyLoader<T>): PluginFactory<T>;\n// Impl names the required-parameter form; the overload narrows to optional when `T` has no\n// required members (see {@link make} / {@link PluginFactory}).\nexport function lazy<T = void>(meta: Meta, loader: LazyLoader<T>): ((options: T) => Plugin) & { meta: Meta } {\n const factory = (options?: T): Plugin => {\n const stub = new PluginImpl(meta, []);\n Object.defineProperty(stub, LazyTag, {\n value: { loader, options } satisfies LazyPayload,\n enumerable: false,\n });\n return stub;\n };\n return Object.assign(factory, { meta });\n}\n\n/**\n * Type guard for lazy plugin stubs produced by {@link lazy}.\n */\nexport const isLazy = (plugin: Plugin): boolean => LazyTag in plugin;\n\n/**\n * Tagged error for failures during lazy plugin resolution. `context.id` is\n * the lazy plugin's `meta.profile.key`; `context.reason` discriminates the failure\n * mode (`'load-failed' | 'missing-default' | 'invalid-plugin' |\n * 'meta-mismatch'`) so callers can route on it.\n */\nexport class LazyPluginError extends BaseError.extend('LazyPluginError', 'Failed to resolve lazy plugin') {}\n\n/**\n * Tagged error for plugin-level dependency resolution failures.\n *\n * `context.id` is the plugin id the manager was acting on. `context.reason`\n * discriminates the failure mode:\n * - `'missing'` — declared dep is neither registered nor in the catalog.\n * `context.missing` lists offending ids.\n * - `'install-failed'` — dep was found in the catalog but `add()` failed.\n * `cause` carries the original error.\n * - `'cycle'` — closure walk detected a cycle. `context.path` is the cycle path.\n * - `'core-dependent'` — cascade-disable would have to disable a core plugin.\n * `context.coreDependent` is the blocking id.\n */\nexport class PluginDependencyError extends BaseError.extend(\n 'PluginDependencyError',\n 'Plugin dependency resolution failed',\n) {}\n\n/**\n * Resolves a lazy plugin stub to its real plugin.\n * Returns the plugin unchanged if it is not lazy. Failures surface as\n * {@link LazyPluginError} with `context.reason` indicating the failure mode\n * and (for loader failures) `cause` set to the original error.\n */\nexport const resolveLazy = (plugin: Plugin): Effect.Effect<Plugin, LazyPluginError> =>\n Effect.gen(function* () {\n if (!isLazy(plugin)) {\n return plugin;\n }\n const id = plugin.meta.profile.key;\n const { loader, options } = (plugin as unknown as { [LazyTag]: LazyPayload })[LazyTag];\n const mod = yield* Effect.tryPromise({\n try: loader,\n catch: (error) => new LazyPluginError({ context: { id, reason: 'load-failed' }, cause: error }),\n });\n if (!mod || typeof mod.default !== 'function') {\n return yield* Effect.fail(new LazyPluginError({ context: { id, reason: 'missing-default' } }));\n }\n const result = mod.default(options);\n if (!isPlugin(result)) {\n return yield* Effect.fail(new LazyPluginError({ context: { id, reason: 'invalid-plugin' } }));\n }\n if (result.meta.profile.key !== id) {\n return yield* Effect.fail(\n new LazyPluginError({ context: { id, reason: 'meta-mismatch', returnedId: result.meta.profile.key } }),\n );\n }\n return result;\n });\n"],"mappings":"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"}
|