@salesforce/metadata-visualizer-web 1.0.1
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/LICENSE.txt +27 -0
- package/README.md +269 -0
- package/dist/WebVisualizationEngine.d.ts +165 -0
- package/dist/WebVisualizationEngine.d.ts.map +1 -0
- package/dist/WebVisualizationEngine.js +284 -0
- package/dist/WebVisualizationEngine.js.map +1 -0
- package/dist/createWebVisualizationEngine.d.ts +27 -0
- package/dist/createWebVisualizationEngine.d.ts.map +1 -0
- package/dist/createWebVisualizationEngine.js +91 -0
- package/dist/createWebVisualizationEngine.js.map +1 -0
- package/dist/design-system/platform.css +623 -0
- package/dist/errors/index.d.ts +25 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +31 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/CompositeFileSystem.d.ts +28 -0
- package/dist/internal/CompositeFileSystem.d.ts.map +1 -0
- package/dist/internal/CompositeFileSystem.js +82 -0
- package/dist/internal/CompositeFileSystem.js.map +1 -0
- package/dist/internal/assetInliner.d.ts +30 -0
- package/dist/internal/assetInliner.d.ts.map +1 -0
- package/dist/internal/assetInliner.js +126 -0
- package/dist/internal/assetInliner.js.map +1 -0
- package/dist/internal/constants.d.ts +20 -0
- package/dist/internal/constants.d.ts.map +1 -0
- package/dist/internal/constants.js +34 -0
- package/dist/internal/constants.js.map +1 -0
- package/dist/internal/htmlPipeline.d.ts +50 -0
- package/dist/internal/htmlPipeline.d.ts.map +1 -0
- package/dist/internal/htmlPipeline.js +86 -0
- package/dist/internal/htmlPipeline.js.map +1 -0
- package/dist/pluginAssets/BundledPluginAssetReader.d.ts +34 -0
- package/dist/pluginAssets/BundledPluginAssetReader.d.ts.map +1 -0
- package/dist/pluginAssets/BundledPluginAssetReader.js +77 -0
- package/dist/pluginAssets/BundledPluginAssetReader.js.map +1 -0
- package/dist/plugins/flexipage/ui/assets/index-BAFqMU7k.js +48 -0
- package/dist/plugins/flexipage/ui/assets/index-U3SB2gLS.css +1 -0
- package/dist/plugins/flexipage/ui/index.html +16 -0
- package/dist/plugins/schema/ui/assets/index-DCLgZby3.css +1 -0
- package/dist/plugins/schema/ui/assets/index-Ho7jg9o_.js +70 -0
- package/dist/plugins/schema/ui/index.html +14 -0
- package/dist/types/HttpAsset.d.ts +18 -0
- package/dist/types/HttpAsset.d.ts.map +1 -0
- package/dist/types/HttpAsset.js +8 -0
- package/dist/types/HttpAsset.js.map +1 -0
- package/dist/types/PluginDataUpdateMessage.d.ts +24 -0
- package/dist/types/PluginDataUpdateMessage.d.ts.map +1 -0
- package/dist/types/PluginDataUpdateMessage.js +8 -0
- package/dist/types/PluginDataUpdateMessage.js.map +1 -0
- package/dist/types/PluginInfo.d.ts +20 -0
- package/dist/types/PluginInfo.d.ts.map +1 -0
- package/dist/types/PluginInfo.js +8 -0
- package/dist/types/PluginInfo.js.map +1 -0
- package/dist/types/VisualizeOutcome.d.ts +19 -0
- package/dist/types/VisualizeOutcome.d.ts.map +1 -0
- package/dist/types/VisualizeOutcome.js +8 -0
- package/dist/types/VisualizeOutcome.js.map +1 -0
- package/dist/types/WebAdapterOptions.d.ts +22 -0
- package/dist/types/WebAdapterOptions.d.ts.map +1 -0
- package/dist/types/WebAdapterOptions.js +8 -0
- package/dist/types/WebAdapterOptions.js.map +1 -0
- package/package.json +77 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2026, Salesforce.com, inc.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
11
|
+
list of conditions and the following disclaimer in the documentation and/or
|
|
12
|
+
other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of Salesforce.com nor the names of its contributors may be
|
|
15
|
+
used to endorse or promote products derived from this software without specific
|
|
16
|
+
prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# @salesforce/metadata-visualizer-web
|
|
2
|
+
|
|
3
|
+
Web/HTTP SDK for the Salesforce Metadata Visualizer framework (`@salesforce/metadata-visualizer-core`). Lets any HTTP-based host (custom web services, browser applications, demo pages) plug a host-supplied filesystem into the framework and serve plugin visualizations to a browser.
|
|
4
|
+
|
|
5
|
+
This package mirrors the role `visualizer-vscode-ext` plays for VS Code: it owns the host-specific plumbing (postMessage bootstrap, plugin asset bundling, `@dist/` URL rewriting) and exposes a small façade so consumers never see plugin internals.
|
|
6
|
+
|
|
7
|
+
## Interfaces the host must implement
|
|
8
|
+
|
|
9
|
+
The SDK is built around the principle of _interface inversion_: we define small contracts in `core-sdk`, and each host (browser application, VS Code, others) implements them. The SDK does not impose a transport, a router, or a filesystem layout — it just consumes what the host supplies and returns blobs the host can serve.
|
|
10
|
+
|
|
11
|
+
There is **exactly one** interface the host must implement: `IFileSystem`. The engine also accepts an optional `telemetry` hook; everything else is handled internally by the SDK.
|
|
12
|
+
|
|
13
|
+
### Required: `IFileSystem` (from `@salesforce/metadata-core-sdk`)
|
|
14
|
+
|
|
15
|
+
This is the only interface the host _must_ implement. It is the host's bridge between the framework and the user-project files the host owns.
|
|
16
|
+
|
|
17
|
+
#### Why this is required
|
|
18
|
+
|
|
19
|
+
Plugin parsers (`SchemaParser`, `FlexipageParser`, future plugins) read the user's metadata files through this interface. The host implements it once against its existing I/O layer — delegating to your project service's file operations, which should enforce path traversal guards, restricted-path rules (`.git`, `.sf`, `node_modules`, dotfiles), path length limits, and per-project isolation.
|
|
20
|
+
|
|
21
|
+
#### Methods to implement
|
|
22
|
+
|
|
23
|
+
| Method | What it does | Implementation notes |
|
|
24
|
+
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `readFile(path)` | Read a file as a UTF-8 string. Returns `Result<string>`. | Delegate to your file I/O layer. Wrap path validation errors into `{ success: false, error }`. |
|
|
26
|
+
| `writeFile(path, content)` | Write a file. Returns `Result<void>`. | Plugins do not write today; you can throw or stub. |
|
|
27
|
+
| `exists(path)` | Whether a file or directory exists. Returns `Promise<boolean>`. | Use your project service's path resolution + file system checks. |
|
|
28
|
+
| `getMetadata(path)` | Returns `{ path, fileName, extension }`. | Pure path operations + stat for existence. |
|
|
29
|
+
| `readDirectory(path)` | Lists directory entries with `{ name, isFile }`. | Delegate to your directory listing implementation. |
|
|
30
|
+
| `findFiles(pattern, maxResults?)` | Workspace-wide glob (e.g. `**/*.object-meta.xml`). | Pattern language is minimatch with POSIX separators, anchored at the workspace root. Schema plugin uses this to discover related objects. |
|
|
31
|
+
| `getWorkspaceRoot()` | Absolute path of the project root. | One project per `IFileSystem` instance — construct one per request bound to that request's project. |
|
|
32
|
+
| `normalizePath`, `separator`, `basename`, `dirname`, `join`, `extname`, `parse`, `relative`, `resolve`, `isAbsolute` | Pure string operations. | Delegate to Node's `path` module. |
|
|
33
|
+
|
|
34
|
+
The full type definition lives at `@salesforce/metadata-core-sdk` → `IFileSystem`.
|
|
35
|
+
|
|
36
|
+
### Optional: `telemetry` (`ITelemetry` from `@salesforce/metadata-core-sdk`)
|
|
37
|
+
|
|
38
|
+
A telemetry hook for tracking usage and errors, passed to `createWebVisualizationEngine` alongside `fileSystem`. If the host doesn't supply it, the SDK falls back to a no-op implementation.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## What the host gets back from the SDK
|
|
43
|
+
|
|
44
|
+
After implementing `IFileSystem` and constructing the engine via `createWebVisualizationEngine`, the host calls these methods. The `WebVisualizationEngine` class provides five core methods:
|
|
45
|
+
|
|
46
|
+
### Core API Methods
|
|
47
|
+
|
|
48
|
+
| Method | Returns | Use Case |
|
|
49
|
+
| -------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
50
|
+
| `getPlugin(metadataFilePath)` | `PluginInfo \| null` | Resolve a metadata file path to its registered plugin (returns `id`, `displayName`, etc.) |
|
|
51
|
+
| `visualizeMetadata(pluginId)` | `{ bundle: HttpAsset<string> \| null, error: PluginError \| null }` | Generate a fully self-contained HTML shell for the plugin's UI (no parsing — UI only) |
|
|
52
|
+
| `getParsedMetadata(metadataFilePath)` | `VisualizeOutcome` — `{ result, error }` | Parse metadata and return structured data without generating HTML |
|
|
53
|
+
| `getUpdatedParsedMetadata(metadataFilePath)` | `{ error, pluginDataUpdateMessage }` | Generate update message for posting to existing plugin iframe (live updates) |
|
|
54
|
+
| `dispose()` | `void` | Release all resources (call on shutdown) |
|
|
55
|
+
|
|
56
|
+
### Method Details
|
|
57
|
+
|
|
58
|
+
**Two-step flow.** UI generation and metadata parsing are separate calls. `visualizeMetadata` produces the static plugin UI shell (no file I/O, no parsing); the loaded iframe then requests parsed data via `postMessage`, which the host serves from `getParsedMetadata`. Use `getPlugin(filePath)` to resolve a metadata file to its plugin ID before calling `visualizeMetadata`.
|
|
59
|
+
|
|
60
|
+
**`getPlugin(metadataFilePath)`** — Resolve plugin for a file:
|
|
61
|
+
|
|
62
|
+
- Matches the file path against registered plugin file patterns
|
|
63
|
+
- Returns `PluginInfo` (with `id`) on match, `null` otherwise
|
|
64
|
+
- Synchronous and side-effect-free
|
|
65
|
+
|
|
66
|
+
**`visualizeMetadata(pluginId)`** — Generate UI shell:
|
|
67
|
+
|
|
68
|
+
- Returns fully self-contained HTML with all assets (JS, CSS, platform styles) inlined
|
|
69
|
+
- **No metadata parsing occurs** — this generates the static plugin UI only
|
|
70
|
+
- Zero additional HTTP requests needed
|
|
71
|
+
- Perfect for iframe embedding: `<iframe srcdoc="..."></iframe>`
|
|
72
|
+
- The loaded plugin iframe requests its data separately via postMessage
|
|
73
|
+
- Error handling: Check `error` field first, then use `bundle`
|
|
74
|
+
|
|
75
|
+
**`getParsedMetadata(metadataFilePath)`** — Parse metadata only:
|
|
76
|
+
|
|
77
|
+
- Returns parsed metadata as structured data
|
|
78
|
+
- Use for REST endpoints, data analysis, validation, and as the data source for plugin postMessage requests
|
|
79
|
+
- No HTML generation overhead
|
|
80
|
+
|
|
81
|
+
**`getUpdatedParsedMetadata(metadataFilePath)`** — Live updates:
|
|
82
|
+
|
|
83
|
+
- Parses file and generates `PluginDataUpdateMessage` ready for iframe postMessage
|
|
84
|
+
- Use with file watchers to push updates to already-loaded visualizations
|
|
85
|
+
- Plugins listen via `onDataUpdate` from `HostCommunicationUtils`
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Integration walkthrough
|
|
90
|
+
|
|
91
|
+
This section explains how a web host integrates the SDK end-to-end. The actual route plumbing depends on your framework conventions (Express, Fastify, etc.). The shape below shows the integration pattern at the seams.
|
|
92
|
+
|
|
93
|
+
### Step 1 — Implement `IFileSystem`
|
|
94
|
+
|
|
95
|
+
The host writes a single class that wraps its existing file I/O layer. Each method delegates to your existing file operations. The class is constructed per request, bound to that request's project root.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// Host implementation — sketch only
|
|
99
|
+
class MyHostFileSystem implements IFileSystem {
|
|
100
|
+
constructor(private readonly projectDir: string) {}
|
|
101
|
+
// readFile, findFiles, exists, getMetadata, readDirectory →
|
|
102
|
+
// delegate to your file service
|
|
103
|
+
// pure-string helpers (basename, join, etc.) → delegate to node:path
|
|
104
|
+
// getWorkspaceRoot() → return this.projectDir
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Step 2 — Construct the engine per request
|
|
109
|
+
|
|
110
|
+
For each incoming HTTP request that needs visualization:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const fileSystem = new MyHostFileSystem(projectDir);
|
|
114
|
+
const engine = await createWebVisualizationEngine({ fileSystem });
|
|
115
|
+
// Use the engine, then dispose when the request ends.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Per-request construction is acceptable for prototypes because plugin loading is cheap. Production hosts can cache engines per project if profiling shows it matters.
|
|
119
|
+
|
|
120
|
+
### Step 3 — Wire HTTP routes
|
|
121
|
+
|
|
122
|
+
The host adds visualizer routes to its service using the five core `WebVisualizationEngine` methods:
|
|
123
|
+
|
|
124
|
+
#### Route 1: Generate HTML Bundle
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
app.post('/visualizer/bundle', async (req, res) => {
|
|
128
|
+
const { filePath } = req.body;
|
|
129
|
+
|
|
130
|
+
// Resolve file → plugin first; visualizeMetadata takes a pluginId, not a path.
|
|
131
|
+
const pluginInfo = engine.getPlugin(filePath);
|
|
132
|
+
if (!pluginInfo) {
|
|
133
|
+
return res.status(415).json({ error: 'No plugin found for this file type' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { bundle, error } = await engine.visualizeMetadata(pluginInfo.id);
|
|
137
|
+
|
|
138
|
+
if (error) {
|
|
139
|
+
const status = error.category === 'Configuration' ? 415 : 500;
|
|
140
|
+
return res.status(status).json({
|
|
141
|
+
error: error.message,
|
|
142
|
+
category: error.category,
|
|
143
|
+
context: error.context,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
res.type(bundle.contentType).set('Cache-Control', bundle.cacheControl).send(bundle.body);
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Use case:** Generate the plugin UI shell for iframe embedding. Returns fully self-contained HTML with all assets inlined. Note: this generates the UI only — the loaded iframe requests parsed metadata separately via postMessage, served by the parse route below.
|
|
152
|
+
|
|
153
|
+
#### Route 2: Parse Metadata
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
app.post('/visualizer/parse', async (req, res) => {
|
|
157
|
+
const { filePath } = req.body;
|
|
158
|
+
const outcome = await engine.getParsedMetadata(filePath);
|
|
159
|
+
|
|
160
|
+
if (outcome.error) {
|
|
161
|
+
return res.status(500).json({
|
|
162
|
+
error: outcome.error.message,
|
|
163
|
+
category: outcome.error.category,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
res.json({
|
|
168
|
+
data: outcome.result.data,
|
|
169
|
+
metadata: outcome.result.metadata,
|
|
170
|
+
filePath: outcome.result.filePath,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Use case:** Data-only endpoint. Plugins request this via postMessage; useful for validation, analysis, and structured data access.
|
|
176
|
+
|
|
177
|
+
#### Route 3: Live Updates
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
app.post('/visualizer/update', async (req, res) => {
|
|
181
|
+
const { filePath } = req.body;
|
|
182
|
+
const { error, pluginDataUpdateMessage } = await engine.getUpdatedParsedMetadata(filePath);
|
|
183
|
+
|
|
184
|
+
if (error) {
|
|
185
|
+
return res.status(500).json({ error: error.message });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Return message for host to post to iframe
|
|
189
|
+
res.json({ updateMessage: pluginDataUpdateMessage, success: true });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// File watcher integration (server-side)
|
|
193
|
+
fileWatcher.on('change', async (filePath) => {
|
|
194
|
+
const { pluginDataUpdateMessage } = await engine.getUpdatedParsedMetadata(filePath);
|
|
195
|
+
if (pluginDataUpdateMessage) {
|
|
196
|
+
// Send to connected clients via WebSocket
|
|
197
|
+
wss.clients.forEach((client) => {
|
|
198
|
+
client.send(JSON.stringify(pluginDataUpdateMessage));
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Use case:** Push updates to already-loaded iframe when file content changes. Plugins listen via `onDataUpdate`:
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import { onDataUpdate } from '@salesforce/metadata-core-sdk/utils/HostCommunicationUtils';
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
const unsubscribe = onDataUpdate((freshData) => {
|
|
211
|
+
setData(freshData); // Re-render with updated data
|
|
212
|
+
});
|
|
213
|
+
return unsubscribe;
|
|
214
|
+
}, []);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Step 4 — Wire the browser side
|
|
218
|
+
|
|
219
|
+
The host's UI mounts an `<iframe>` whose `src` is the bundle URL above. Because the iframe is same-origin (everything served from your domain), the bootstrap script the SDK injected can `postMessage` cleanly to the parent window. The parent UI listens for those messages and proxies `REQUEST_PLUGIN_DATA` back to the parse endpoint, then posts `PLUGIN_DATA_RESPONSE` back into the iframe.
|
|
220
|
+
|
|
221
|
+
This relay logic lives entirely on the host's side. The package gives the iframe a working `__ExtensionHostPostMessage` and an HTML document; the host decides what listens to those messages on the parent.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Architecture rules enforced by tests
|
|
226
|
+
|
|
227
|
+
The SDK has two strict architectural rules — both checked at test time:
|
|
228
|
+
|
|
229
|
+
1. **No runtime dependency on `@salesforce/metadata-plugins`.** Plugin React builds are bundled into the SDK's own `dist/plugins/` at build time via `scripts/copy-plugin-assets.mjs` — same pattern the VS Code adapter uses with webpack `CopyPlugin`. The plugins package is referenced only at the workspace root's `devDependencies`. Consumers' `npm install` never pulls plugins in directly.
|
|
230
|
+
2. **No `vscode` import.** This is a browser-host SDK; VS Code-flavored code stays in `visualizer-vscode-ext`.
|
|
231
|
+
|
|
232
|
+
Both are enforced by `src/__tests__/architecture.test.ts`.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
npm run build # tsc + scripts/copy-plugin-assets.mjs (populates dist/plugins/, dist/design-system/)
|
|
240
|
+
npm test # jest with coverage
|
|
241
|
+
npm run typecheck # tsc --noEmit
|
|
242
|
+
npm run lint
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Build runs from the workspace root — `@salesforce/metadata-plugins` lives in the root's `devDependencies` and the build script resolves it from there. Building the package outside the workspace is not supported.
|
|
246
|
+
|
|
247
|
+
## Public API summary
|
|
248
|
+
|
|
249
|
+
### Core Class
|
|
250
|
+
|
|
251
|
+
- `WebVisualizationEngine` — Main class with five methods:
|
|
252
|
+
- `getPlugin(metadataFilePath)` — Resolve a file path to its registered plugin
|
|
253
|
+
- `visualizeMetadata(pluginId)` — Generate the plugin UI shell (HTML bundle, no parsing)
|
|
254
|
+
- `getParsedMetadata(metadataFilePath)` — Parse metadata only
|
|
255
|
+
- `getUpdatedParsedMetadata(metadataFilePath)` — Generate update message for live updates
|
|
256
|
+
- `dispose()` — Release resources
|
|
257
|
+
- `createWebVisualizationEngine(opts)` — Async factory function
|
|
258
|
+
|
|
259
|
+
### Supporting Types
|
|
260
|
+
|
|
261
|
+
- `HttpAsset<T>` — Response wrapper: `{ contentType, body, cacheControl }`
|
|
262
|
+
- `PluginInfo` — Plugin metadata: `{ id, displayName, filePatterns, priority? }`
|
|
263
|
+
- `VisualizeOutcome` — Parse result: `{ result, error }`. The `error` field is a `PluginError` from `@salesforce/metadata-core-sdk`.
|
|
264
|
+
- `PluginDataUpdateMessage<T = unknown>` — Update message for postMessage: `{ type: 'PLUGIN_DATA_UPDATE', payload: PluginDataResponse<T> }`. Generic `T` lets plugin code with a known data shape opt into type-safety; consumers can narrow on `message.type` in a discriminated-union switch.
|
|
265
|
+
- `WebAdapterOptions` — Engine constructor options: `{ fileSystem, telemetry? }`
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
BSD-3-Clause.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* Licensed under the BSD 3-Clause license.
|
|
5
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
**/
|
|
7
|
+
import type { PluginError } from '@salesforce/metadata-core-sdk';
|
|
8
|
+
import type { PluginManager, VisualizationEngine } from '@salesforce/metadata-visualizer-core';
|
|
9
|
+
import { BundledPluginAssetReader } from './pluginAssets/BundledPluginAssetReader.js';
|
|
10
|
+
import type { HttpAsset } from './types/HttpAsset.js';
|
|
11
|
+
import type { PluginInfo } from './types/PluginInfo.js';
|
|
12
|
+
import type { VisualizeOutcome } from './types/VisualizeOutcome.js';
|
|
13
|
+
import type { PluginDataUpdateMessage } from './types/PluginDataUpdateMessage.js';
|
|
14
|
+
/**
|
|
15
|
+
* Internal construction parameters used by the factory. Not part of the
|
|
16
|
+
* public API — consumers always go through `createWebVisualizationEngine`.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export interface WebVisualizationEngineDeps {
|
|
21
|
+
framework: VisualizationEngine;
|
|
22
|
+
pluginManager: PluginManager;
|
|
23
|
+
pluginAssetReader: BundledPluginAssetReader;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Browser/HTTP platform adapter for the visualization framework.
|
|
27
|
+
* Provides methods to generate self-contained HTML bundles and parse metadata
|
|
28
|
+
* for embedding in iframes. All methods are safe to call concurrently.
|
|
29
|
+
*/
|
|
30
|
+
export declare class WebVisualizationEngine {
|
|
31
|
+
private readonly framework;
|
|
32
|
+
private readonly pluginManager;
|
|
33
|
+
private readonly assetReader;
|
|
34
|
+
/** Tracks whether dispose() has been called to prevent use-after-dispose */
|
|
35
|
+
private disposed;
|
|
36
|
+
/** Per-file monotonic counter for fileId. Date.now() collides under sub-ms concurrent calls. */
|
|
37
|
+
private readonly fileIdCounters;
|
|
38
|
+
/**
|
|
39
|
+
* Construct an engine.
|
|
40
|
+
*
|
|
41
|
+
* **Use `createWebVisualizationEngine()` instead.** This constructor is exposed
|
|
42
|
+
* only for the factory and internal tests, and it asserts an ownership contract
|
|
43
|
+
* that is easy to misuse:
|
|
44
|
+
*
|
|
45
|
+
* - The injected `framework` (and its `ErrorManager`) is **owned** by the
|
|
46
|
+
* resulting engine. Calling `dispose()` tears it down — including any error
|
|
47
|
+
* listeners registered by other code on the same instance.
|
|
48
|
+
* - Therefore the framework must be dedicated to this engine. Sharing one
|
|
49
|
+
* `VisualizationEngine` between a `WebVisualizationEngine` and another
|
|
50
|
+
* adapter (e.g. the VS Code adapter) will break the other adapter when this
|
|
51
|
+
* engine is disposed.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
constructor(deps: WebVisualizationEngineDeps);
|
|
56
|
+
/**
|
|
57
|
+
* Generate a complete HTML bundle for a plugin's visualization UI.
|
|
58
|
+
* Returns a self-contained HTML file with all assets (JS, CSS, platform styles) inlined,
|
|
59
|
+
* ready to be embedded in an iframe. No metadata parsing occurs — this generates the static UI shell.
|
|
60
|
+
*
|
|
61
|
+
* @param pluginId - ID of the registered plugin to generate bundle for
|
|
62
|
+
* @returns Object containing the bundle (null if failed) and error (null if successful)
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const { bundle, error } = await engine.visualizeMetadata('flexipage-visualizer');
|
|
67
|
+
* if (error) {
|
|
68
|
+
* console.error('Bundle generation failed:', error.message);
|
|
69
|
+
* res.status(error.category === 'Configuration' ? 404 : 500).json({ error: error.message });
|
|
70
|
+
* } else if (bundle) {
|
|
71
|
+
* res.type(bundle.contentType).send(bundle.body);
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
visualizeMetadata(pluginId: string): Promise<{
|
|
76
|
+
bundle: HttpAsset<string> | null;
|
|
77
|
+
error: PluginError | null;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Parse a metadata file and return the visualization outcome.
|
|
81
|
+
* Finds the appropriate plugin by file pattern, parses the file, and returns structured data.
|
|
82
|
+
* Handles error capturing for concurrent-safe execution (multiple calls won't interfere).
|
|
83
|
+
*
|
|
84
|
+
* @param metadataFilePath - Path to the metadata file to parse
|
|
85
|
+
* @returns VisualizeOutcome containing either parsed result or error
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const outcome = await engine.getParsedMetadata('force-app/main/default/flexipages/Home.flexipage');
|
|
90
|
+
* if (outcome.error) {
|
|
91
|
+
* console.error('Parse failed:', outcome.error.message);
|
|
92
|
+
* } else {
|
|
93
|
+
* console.log('Parsed data:', outcome.result.data);
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
getParsedMetadata(metadataFilePath: string): Promise<VisualizeOutcome>;
|
|
98
|
+
/**
|
|
99
|
+
* Parse metadata and generate an update message for posting to an existing plugin iframe.
|
|
100
|
+
* Use this when file content changes and you need to push updates to a loaded iframe
|
|
101
|
+
* without regenerating the entire bundle. Plugins receive updates via onDataUpdate().
|
|
102
|
+
*
|
|
103
|
+
* @param metadataFilePath - Path to the metadata file to parse
|
|
104
|
+
* @returns Object containing error (if any) and pluginDataUpdateMessage (null if parsing failed)
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // File watcher integration
|
|
109
|
+
* watcher.on('change', async (filePath) => {
|
|
110
|
+
* const { error, pluginDataUpdateMessage } = await engine.getUpdatedParsedMetadata(filePath);
|
|
111
|
+
* if (pluginDataUpdateMessage) {
|
|
112
|
+
* iframeWindow.postMessage(pluginDataUpdateMessage, '*');
|
|
113
|
+
* }
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
getUpdatedParsedMetadata(metadataFilePath: string): Promise<{
|
|
118
|
+
error: PluginError | null;
|
|
119
|
+
pluginDataUpdateMessage: PluginDataUpdateMessage | null;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Release all resources held by the engine.
|
|
123
|
+
* After calling this method, the engine instance should not be used.
|
|
124
|
+
* Safe to call multiple times (idempotent).
|
|
125
|
+
*
|
|
126
|
+
* **Disposes the underlying framework.** The engine takes ownership of the
|
|
127
|
+
* `VisualizationEngine` passed to its constructor (or built by the factory),
|
|
128
|
+
* including its `ErrorManager`. Do not share that framework with another
|
|
129
|
+
* adapter — it will be torn down here.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* process.on('SIGTERM', () => {
|
|
134
|
+
* engine.dispose();
|
|
135
|
+
* process.exit(0);
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
dispose(): void;
|
|
140
|
+
/**
|
|
141
|
+
* Create a PLUGIN_DATA_UPDATE message for posting to a loaded plugin iframe.
|
|
142
|
+
* Per-file monotonic counter guarantees a unique, ordered fileId per update,
|
|
143
|
+
* even under sub-millisecond concurrent calls (Date.now() can collide).
|
|
144
|
+
*
|
|
145
|
+
* @param outcome - Visualization outcome with parsed data
|
|
146
|
+
* @returns Message ready to be posted via iframe.contentWindow.postMessage(), or null if parsing failed
|
|
147
|
+
*/
|
|
148
|
+
private createDataUpdateMessage;
|
|
149
|
+
/**
|
|
150
|
+
* Find the registered plugin that handles a given file.
|
|
151
|
+
* Matches file against plugin patterns (e.g., *.flexipage, *.object, *-meta.xml).
|
|
152
|
+
*
|
|
153
|
+
* @param metadataFile - Filename (e.g., 'Home.flexipage') or full path to match
|
|
154
|
+
* @returns PluginInfo if a matching plugin is found, null otherwise
|
|
155
|
+
*/
|
|
156
|
+
getPlugin(metadataFile: string): PluginInfo | null;
|
|
157
|
+
/**
|
|
158
|
+
* Verify a plugin is registered before attempting to use it.
|
|
159
|
+
* Prevents asset read failures for non-existent plugins.
|
|
160
|
+
*/
|
|
161
|
+
private requireKnownPlugin;
|
|
162
|
+
/** Guards against use-after-dispose by throwing an error */
|
|
163
|
+
private assertNotDisposed;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=WebVisualizationEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebVisualizationEngine.d.ts","sourceRoot":"","sources":["../src/WebVisualizationEngine.ts"],"names":[],"mappings":"AAAA;;;;;IAKI;AAEJ,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAI/F,OAAO,EAAE,wBAAwB,EAAE,MAAM,4CAA4C,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAElF;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,wBAAwB,CAAC;CAC7C;AAED;;;;GAIG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAS;IACzB,gGAAgG;IAChG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAE5D;;;;;;;;;;;;;;;;OAgBG;gBACS,IAAI,EAAE,0BAA0B;IAM5C;;;;;;;;;;;;;;;;;;OAkBG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACjD,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;QACjC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;KAC3B,CAAC;IA0DF;;;;;;;;;;;;;;;;;OAiBG;IACG,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAyC5E;;;;;;;;;;;;;;;;;;OAkBG;IACG,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC;QAChE,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;QAC1B,uBAAuB,EAAE,uBAAuB,GAAG,IAAI,CAAC;KACzD,CAAC;IASF;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,IAAI,IAAI;IAUf;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAKlD;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAI1B,4DAA4D;IAC5D,OAAO,CAAC,iBAAiB;CAK1B"}
|