feat(plugins): add host.storage bridge and document sidecar data dir
This commit is contained in:
18 files changed
+700
-8
No files matched your search
@@ -103,6 +103,7 @@ Manifest 中的路径相对于包根目录,不能以 `/` 开头、不能包含
|
||||
"host.filesystem",
|
||||
"host.binary",
|
||||
"host.plans:read",
|
||||
"host.storage",
|
||||
"host.network:https://s3.example.com:443"
|
||||
],
|
||||
"entrypoints": {
|
||||
@@ -119,6 +120,7 @@ Manifest 中的路径相对于包根目录,不能以 `/` 开头、不能包含
|
||||
- `engines.dbx` 是可选的 DBX 版本范围;`engines.host_api` 是必需的 Host API 版本声明。
|
||||
- `host.workbench` 允许打开另一个工作台,`host.events` 接收宿主事件,`host.binary` 使用二进制通道,`host.filesystem` 使用文件系统入口。
|
||||
- `host.plans:read` 允许针对 DBX 自己的连接读取估算执行计划(见 [估算执行计划](#估算执行计划))。它是只读权限:不授予普通 SQL 执行、写操作、DDL 或真实执行计划,且这是唯一存在的计划类权限。
|
||||
- `host.storage` 为工作台 UI 提供本插件独享的持久化键值存储(见 [持久化 UI 状态](#持久化-ui-状态));数据只落在插件自己的数据目录里。
|
||||
- `host.network:https://host[:port]` 声明浏览器可访问的 HTTPS origin,DBX 将它加入 CSP 的 `connect-src`;最多 8 个,不允许路径、通配符或 Token。仍受目标服务 CORS 规则约束。该权限不是原生 Sidecar 的网络防火墙。
|
||||
- `backend.transport` 默认是 `stdio-jsonl`;需要二进制帧时使用 `stdio-framed`,并同时声明 `host.binary`。
|
||||
- `backend.executable`、`ui.root` 和 `ui.entry` 必须指向包内文件;带原生后端的包必须为每个目标平台提供对应可执行文件。
|
||||
@@ -295,8 +297,9 @@ const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
|
||||
| `openFilesystem(id, context)` | 打开本插件的文件系统入口;需要 `host.filesystem`。 |
|
||||
| `getPlanCapabilities(connectionId)` | 读取宿主与该连接能提供什么计划能力;需要 `host.plans:read`。 |
|
||||
| `explainPlan({ connectionId, database?, schema?, sql, mode, timeoutMs? })` | 返回 `sql` 的估算执行计划;需要 `host.plans:read`。 |
|
||||
| `capabilities` | 来自 init 消息的 `{ downloadFile, planApi }`。字段缺失或为 `false` 表示当前宿主不支持该组 Host API,应据此关闭功能而不是用真实请求试探。 |
|
||||
| `capabilities` | 来自 init 消息的 `{ downloadFile, planApi, storage }`。字段缺失或为 `false` 表示当前宿主不支持该组 Host API,应据此关闭功能而不是用真实请求试探。 |
|
||||
| `onInit(fn)` / `onEvent(fn)` | 监听初始化、环境变化或后端事件;转发后端事件需要 `host.events`。 |
|
||||
| `storage` | 本插件独享的持久化键值状态;需要 `host.storage`;见下文。 |
|
||||
| `fileTransfer` | 本地文件流式传输与系统拖放(仅桌面端);见下文。 |
|
||||
|
||||
调用失败会以 Promise rejection 返回,处理时应展示可理解的错误并允许重试。通过 `request` 调用的宿主方法、参数和返回值以当前 Host API 版本为准,不要调用未在协议中声明的 DBX 内部函数。
|
||||
@@ -392,6 +395,27 @@ const offDrag = fileTransfer.onDragState((active) => showDropOverlay(active));
|
||||
|
||||
仅桌面端可用:Web 宿主下整个命名空间是 `undefined`,使用前请先探测。`onDrop` 只在拖放落点位于本插件工作台区域内时触发;落点在宿主其他区域时保持宿主自身的行为。
|
||||
|
||||
### 持久化 UI 状态
|
||||
|
||||
沙箱是 opaque origin,`localStorage` 在里面会直接抛错。需要跨重载或跨应用重启保留状态的工作台(最近访问的目录、面板尺寸、草稿等)改用 `window.dbxPlugin.storage`。它需要 `host.storage` 权限,并以 init 消息里的 `capabilities.storage` 广播;使用前请同时探测这两者。
|
||||
|
||||
```js
|
||||
if (window.dbxPlugin.capabilities.storage) {
|
||||
const storage = window.dbxPlugin.storage;
|
||||
await storage.set("layout", { sidebar: "collapsed", sort: "name" });
|
||||
const layout = await storage.get("layout"); // { sidebar: "collapsed", sort: "name" },未设置时为 null
|
||||
await storage.delete("layout");
|
||||
}
|
||||
```
|
||||
|
||||
| API | 用途 |
|
||||
| --- | --- |
|
||||
| `get(key)` | 返回存储的 JSON 值;键未设置时返回 `null`。 |
|
||||
| `set(key, value)` | 持久化任意 JSON 值(`undefined` 归一化为 `null`)。 |
|
||||
| `delete(key)` | 删除一个键;删除不存在的键同样成功。 |
|
||||
|
||||
键为不超过 256 字符的字符串;单个值序列化后上限 256 KiB,整库上限 1 MiB——这是 UI 状态存储,不是数据落盘通道。数据保存在插件自己的 `plugin-data/<id>` 目录里,按插件隔离,版本升级和卸载后都保留。大批量数据、缓存以及 Sidecar 产生的任何文件请放进 Sidecar 的数据目录(见 [原生 Sidecar 和 RPC](#原生-sidecar-和-rpc))。Web 宿主上同一 API 由浏览器本地存储支撑,两端代码完全一致。
|
||||
|
||||
### 主题、布局和静态资源
|
||||
|
||||
DBX 将主题 tokens 注入文档根元素,并更新 `data-dbx-theme`。自定义组件库(包括 Svelte、shadcn-svelte)应使用这些变量,不要依赖父页面 CSS 自动穿透 iframe:
|
||||
@@ -471,6 +495,8 @@ fn main() -> std::io::Result<()> {
|
||||
|
||||
Sidecar 约束:stdout 只能输出协议消息,日志写 stderr;请求 ID 必须支持并发关联;连接和断开应幂等;长任务应设计超时、取消和分块确认;不要在事件、context 或错误消息中泄露 Secret。
|
||||
|
||||
每个 Sidecar 启动时都会带上 `DBX_PLUGIN_DATA_DIR` 环境变量,指向为本插件保留的持久化目录(`plugin-data/<id>`,与安装包目录平级)。它在版本升级和卸载后都保留,插件的所有 Sidecar 运行共享;缓存、Token、后端管理的数据库等应放在这里——首次写入前需自行创建目录,宿主不会预创建。Go SDK 提供了 `dbxpluginsdk.DataDir()` / `EnsureDataDir()` 封装。工作台 UI 需要小体量持久化时走同一目录里的 `host.storage`(见 [持久化 UI 状态](#持久化-ui-状态)),不要直接读写这些文件。
|
||||
|
||||
上例可替换 Rust 模板的 `backend/src/main.rs`,同时让 Cargo 包版本与 Manifest 一致。Go 使用模板生成的 `dbxpluginsdk.NewServer(metadata, handler).Serve()`;当前 Go SDK 支持 JSONL,Rust SDK 还支持 framed transport。不要仅修改 Manifest transport 就认为 Go 已具备二进制帧处理能力。完整接口见 [Rust SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/rust/dbx-plugin-sdk) 和 [Go SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/go/dbx-plugin-sdk)。
|
||||
|
||||
## 创建插件
|
||||
|
||||
@@ -103,6 +103,7 @@ Manifest paths are relative to the package root. They cannot start with `/`, con
|
||||
"host.filesystem",
|
||||
"host.binary",
|
||||
"host.plans:read",
|
||||
"host.storage",
|
||||
"host.network:https://s3.example.com:443"
|
||||
],
|
||||
"entrypoints": {
|
||||
@@ -119,6 +120,7 @@ Manifest paths are relative to the package root. They cannot start with `/`, con
|
||||
- `engines.dbx` is an optional DBX version range; `engines.host_api` is required.
|
||||
- `host.workbench` opens another workbench, `host.events` receives host events, `host.binary` enables binary channels, and `host.filesystem` enables filesystem entrypoints.
|
||||
- `host.plans:read` allows reading estimated execution plans for DBX's own connections (see [Estimated Execution Plans](#estimated-execution-plans)). It is read-only: it never grants SQL execution, writes, DDL, or actual plans, and it is the only plan scope that exists.
|
||||
- `host.storage` gives the workbench UI a persistent key-value store scoped to this plugin (see [Persistent UI State](#persistent-ui-state)). Values never leave the plugin's own data directory.
|
||||
- `host.network:https://host[:port]` declares a browser-accessible HTTPS origin added to CSP `connect-src`; at most eight origins are allowed, with no paths, wildcards, or tokens. The service's CORS rules still apply. This permission is not a native Sidecar network firewall.
|
||||
- `backend.transport` defaults to `stdio-jsonl`. Use `stdio-framed` for binary frames and declare `host.binary` as well.
|
||||
- `backend.executable`, `ui.root`, and `ui.entry` must point to package files. Native packages need a matching executable for every target.
|
||||
@@ -295,8 +297,9 @@ const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
|
||||
| `openFilesystem(id, context)` | Opens a filesystem entry; requires `host.filesystem`. |
|
||||
| `getPlanCapabilities(connectionId)` | Reads what the host and this connection can plan; requires `host.plans:read`. |
|
||||
| `explainPlan({ connectionId, database?, schema?, sql, mode, timeoutMs? })` | Returns the estimated plan for `sql`; requires `host.plans:read`. |
|
||||
| `capabilities` | `{ downloadFile, planApi }` from the init message. A missing or `false` entry means that Host API group is unavailable here, so gate the matching call on it instead of probing with a request. |
|
||||
| `capabilities` | `{ downloadFile, planApi, storage }` from the init message. A missing or `false` entry means that Host API group is unavailable here, so gate the matching call on it instead of probing with a request. |
|
||||
| `onInit(fn)` / `onEvent(fn)` | Observes initialization, environment changes, or backend events; forwarding backend events requires `host.events`. |
|
||||
| `storage` | Persistent per-plugin key-value state; requires `host.storage`; see below. |
|
||||
| `fileTransfer` | Streams local files and OS drops (desktop only); see below. |
|
||||
|
||||
Failed calls reject their Promise; show a useful error and offer retry. Host methods, parameters, and results are versioned API surface—do not call undocumented DBX internals.
|
||||
@@ -392,6 +395,27 @@ const offDrag = fileTransfer.onDragState((active) => showDropOverlay(active));
|
||||
|
||||
Desktop only: the whole namespace is `undefined` in the web host, so feature-detect before use. `onDrop` only fires for drops that land inside this plugin's workbench area; drops elsewhere keep the host's own behavior.
|
||||
|
||||
### Persistent UI State
|
||||
|
||||
The sandbox has an opaque origin, so `localStorage` throws there. Workbenches that need to survive a reload or an app restart — last visited folder, panel sizes, drafts — use `window.dbxPlugin.storage` instead. It requires the `host.storage` permission and is advertised as `capabilities.storage` in the init message; gate on both before use.
|
||||
|
||||
```js
|
||||
if (window.dbxPlugin.capabilities.storage) {
|
||||
const storage = window.dbxPlugin.storage;
|
||||
await storage.set("layout", { sidebar: "collapsed", sort: "name" });
|
||||
const layout = await storage.get("layout"); // { sidebar: "collapsed", sort: "name" }, or null when unset
|
||||
await storage.delete("layout");
|
||||
}
|
||||
```
|
||||
|
||||
| API | Purpose |
|
||||
| --- | --- |
|
||||
| `get(key)` | Resolves the stored JSON value, or `null` when the key is unset. |
|
||||
| `set(key, value)` | Persists any JSON value (`undefined` normalizes to `null`). |
|
||||
| `delete(key)` | Removes one key; deleting an unset key succeeds. |
|
||||
|
||||
Keys are strings up to 256 characters; values are capped at 256 KiB serialized and the whole store at 1 MiB — this is UI state, not a data sink. Entries live in the plugin's own `plugin-data/<id>` directory, isolated per plugin, and survive upgrades and uninstall. Bulk data, caches, and anything the sidecar produces belong in the sidecar's data directory instead (see [Native Sidecars and RPC](#native-sidecars-and-rpc)). On the web host the same API is backed by the browser profile, so identical code works in both hosts.
|
||||
|
||||
### Themes, Layout, and Static Assets
|
||||
|
||||
DBX injects theme tokens on the document root and updates `data-dbx-theme`. Custom libraries, including Svelte and shadcn-svelte, should consume these variables; parent-page CSS does not cross the iframe:
|
||||
@@ -471,6 +495,8 @@ fn main() -> std::io::Result<()> {
|
||||
|
||||
Sidecar rules: reserve stdout for protocol messages and write logs to stderr; correlate concurrent requests by ID; make connect/disconnect idempotent; design timeouts, cancellation, and chunk acknowledgements for long tasks; never expose secrets through events, context, or error messages.
|
||||
|
||||
Every sidecar starts with `DBX_PLUGIN_DATA_DIR` set to a persistent directory reserved for this plugin (`plugin-data/<id>`, a sibling of the installed package). It survives version upgrades and uninstall, is shared by all of the plugin's sidecar runs, and is the right home for caches, tokens, and databases the backend manages — create it before the first write; the host does not pre-create it. The Go SDK exposes it as `dbxpluginsdk.DataDir()` / `EnsureDataDir()`. Workbench UIs reach a small JSON-backed store inside the same directory through `host.storage` instead of touching these files (see [Persistent UI State](#persistent-ui-state)).
|
||||
|
||||
The example can replace the Rust template's `backend/src/main.rs`; keep its Cargo package version equal to the Manifest. Go templates use `dbxpluginsdk.NewServer(metadata, handler).Serve()`. The current Go SDK supports JSONL; the Rust SDK also supports framed transport. Changing a Go Manifest's transport alone does not implement binary framing. See the [Rust SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/rust/dbx-plugin-sdk) and [Go SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/go/dbx-plugin-sdk) for complete interfaces.
|
||||
|
||||
## Create a Plugin
|
||||
|
||||
Reference in new issue
Block a user