> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arupa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# KV 存储

> 插件可以使用内核共享的内存键值存储。

内核为所有正在运行的插件提供了一个共享的键值存储。
插件可以使用它来存储必须通过内核读取或更新的小段状态，例如缓存值、插件状态或与其他插件共享的数据。
KV 值是原始字节。内核不会为其指定数据格式，因此您可以将值编码为 JSON、文本、二进制结构或插件能够理解的任何其他格式。

## 存储模型

该存储采用两级结构：

```text theme={null}
命名空间 → 键 → 字节值
```

例如：

```text theme={null}
命名空间：preferences
键：theme
值：dark
```

KV 存储属于内核，而不是单个插件实例。所有连接到同一内核的插件都使用同一个存储。
插件命名空间是一种组织约定，而非权限边界。在当前实现中，除非另一个插件的命名空间是只读的(当前仅有`sys`)，否则插件可以读取和写入该命名空间。
按照惯例，使用插件拥有的命名空间，该命名空间通常由插件名称派生而来。如果多个插件有意共享一个命名空间，请记录它们约定使用的键名和值格式。

## 操作

操作由 Protobuf 主机契约定义。有关共享传输的定义，请参阅[Protobuf](../proto)。

### 读取值

读取请求包含：

| 字段          | 类型       | 含义        |
| ----------- | -------- | --------- |
| `namespace` | `string` | 包含键的命名空间。 |
| `key`       | `string` | 要读取的键。    |

响应包含一个 `found` 布尔值和一个 `value` 字节序列。缺少键并非错误：`found` 为 `false`，且不返回任何值。

读取值不会创建其命名空间或键。

### 写入值

写入请求包含相同的 `namespace` 和 `key` 字段，以及：

| 字段      | 类型      | 含义       |
| ------- | ------- | -------- |
| `value` | `bytes` | 要存储的原始值。 |

写入现有键会替换其值。如果写入操作被拒绝，
例如命名空间为只读时，该操作将返回错误字符串。

### 删除值

删除请求包含 `namespace` 和 `key`。删除现有键会移除其值。
删除不存在的键会成功，且不会更改存储。

如果删除操作被拒绝，包括尝试从只读命名空间中删除，
该操作将返回错误字符串。

### 列出键

列表请求包含一个 `namespace` 字段：

* 当 `namespace` 非空时，响应包含当前该命名空间中存在的键。
* 当 `namespace` 为空时，响应包含当前存储中所有命名空间的名称。

返回的名称按内核排序。列表不会返回值。

## 生命周期和持久化

内核在创建插件管理器时会创建 KV 存储。该存储位于内存中：

* 停止或重启插件不会清除其 KV 值；
* 加载同一插件的新版本不会清除其 KV 值；并且
* 重启内核会创建一个新的空存储并清除所有值。

因此，KV 不是持久数据库。当数据必须在内核重启后仍然存在时，
请将其持久化到其他位置，并将 KV 视为运行时状态或共享缓存。

## 并发性和值所有权

内核会同步各个键值 (KV) 操作。当多个插件同时调用时，
读取、写入、删除和列表操作都是安全的。

不存在事务或比较交换操作。

诸如“读取、修改、写入”之类的操作序列对于其他插件而言并非原子操作。
如果多个插件更新同一个键，则需要协调所有权或设计该值，
以确保丢失的更新不会破坏应用程序状态。

内核在存储和读取字节值时会进行复制。
写入后修改字节缓冲区不会改变存储的值，
修改返回的缓冲区也不会改变存储的值。

## `sys` 命名空间

`sys` 是一个由内核管理的只读命名空间。插件可以读取和列出它，
但通过插件宿主边界进行的写入和删除操作会返回错误。
内核使用此命名空间来管理宿主状态，包括插件注册表信息。
不要写入 `sys`，也不要依赖未记录的系统键，除非内核文档明确定义了它们。

## 后端行为

WASM 和 gRPC 插件使用相同的键值对 (KV) 约定：

* WASM 插件通过生成的主机绑定访问存储；以及
* gRPC 插件通过内核的主机回调通道访问存储。

两种后端的存储语义相同。后端改变的是请求如何跨越插件边界，而不是内核如何存储或返回值。

## 推荐用法

使用可预测的命名空间和键方案：

```text theme={null}
namespace: my-plugin
keys:
config/theme
cache/user/<id>
state/last-sync
```

保持值足够小，以便于调用路径，并在其他组件使用它们之前定义它们的编码。
在解码值之前检查 `found` 结果处理写入和删除错误，并避免将成功的写入视为持久化。
