记录存储
db.collection —— 每个插件的文档存储,支持查询、全文搜索和实时更新的 UI。
对于超过少量键值对的情况,Kunkun 为每个扩展提供真正的记录存储:db.collection<T>(name)。它是面向行的、可查询的、支持全文搜索、可分页且响应式的——后端由 SQLite + FTS5 驱动。
为什么不是 LocalStorage?
LocalStorage 适合少量标志。但是在 KV 中存储列表迫使你每次更改都要重写一个巨大的 JSON 对象,或者散落一堆无法筛选或分页的 key:id 条目。记录存储解决了这两个问题。两个 API 共享 storage 权限。
获取集合
import { db } from "@kunkunsh/sdk";
interface Bookmark { url: string; title: string; tags: string[]; visits: number }
const bookmarks = db.collection<Bookmark>("bookmarks");集合在首次写入时创建。名称需匹配 ^[a-z0-9][a-z0-9-_]{0,63}$。
写入
const id = await bookmarks.add({ url: "https://kunkun.sh", title: "Kunkun", tags: ["dev"], visits: 0 });
await bookmarks.set("my-id", data); // upsert,完全替换
await bookmarks.update(id, { visits: 5 }); // 浅合并
await bookmarks.delete(id); // 幂等
await bookmarks.bulkSet([{ data }, { data, searchText: "…" }]); // 最多 1000 条,单事务
await bookmarks.bulkDelete([id1, id2]);
await bookmarks.clear(); // 清空集合add 生成一个 ULID 格式的 id;你也可以通过 set / bulkSet 提供自己的 id(≤128 字符)。
读取
const rec = await bookmarks.get(id); // DbRecord<Bookmark> | undefined
const recs = await bookmarks.getMany([a, b]); // 保持顺序,缺失的 id 会被省略每条记录都有一个稳定的信封结构:
interface DbRecord<T> {
id: string; // ULID(或你的 id)
data: T; // 你的 JSON 文档
createdAt: string; // ISO 格式,首次插入时设置
updatedAt: string; // ISO 格式,每次写入时更新
}查询
const page = await bookmarks.query({
where: [["visits", ">=", 10], ["tags", "contains", "dev"]],
search: "github", // FTS5 搜索 searchText(见下文)
orderBy: [["visits", "desc"]],
limit: 50,
cursor: prev?.nextCursor, // 键集分页
});
// => { items: DbRecord<Bookmark>[], nextCursor?: string }
const n = await bookmarks.count({ where: [["tags", "contains", "dev"]] });运算符
| 运算符 | 含义 |
|---|---|
== != | 相等(与 null 比较时匹配 JSON null 或缺失字段) |
< <= > >= | 比较 |
in | 字段是列表中的一项(≤100 个值) |
contains | 数组字段包含该值 |
- 字段路径 使用点符号进入你的文档:
["author.name", "==", "Ada"]。 - 多个
where子句是 AND 关系(≤10 个子句;OR 已计划)。 orderBy接受createdAt、updatedAt、id或 JSON 路径;id总是作为决胜字段追加(默认排序为id desc→ 最新的 ULID 优先)。- 游标 是不透明的键集令牌,仅对相同的
where/orderBy形状有效——将nextCursor传回以获取下一页。
ULID 是关键的
默认情况下 id 是 ULID:设备间无冲突且按字典序创建顺序排序。这就是"最新优先"不需要额外时间戳列的原因——也是使存储无需更改代码即可支持云同步的原因。
全文搜索
搜索是每条记录可选的,通过写入时提供的 searchText 字段实现;查询时 FTS5 匹配它:
await notes.add({ title: "购买食材", body: "牛奶、鸡蛋" }, { searchText: "购买食材 牛奶 鸡蛋" });
const hits = await notes.query({ search: "牛奶" });响应式
watch
const unsubscribe = bookmarks.watch((event) => {
// event: { collection, ids, op: "set" | "delete" | "clear", timestamp }
});
db.watch((event) => { /* 此插件中的任意集合 */ });宿主在每次提交写入后发出 db:changed 事件,传递给你的扩展(无论其运行在 desktop custom-view、worker relay 还是 CLI/web 下)。
实时查询(推荐)
不要手动连接 watch + 重新获取——绑定查询并让它自动更新。
Svelte(createLiveQuery 是一个 store):
<script lang="ts">
import { createLiveQuery } from "@kunkunsh/sdk";
const q = createLiveQuery<Bookmark>("bookmarks", { orderBy: [["visits", "desc"]] });
</script>
{#if $q.isLoading}加载中…{:else}
{#each $q.records as r (r.id)}<div>{r.data.title}</div>{/each}
{#if $q.nextCursor}<button onclick={() => q.loadMore()}>更多</button>{/if}
{/if}React(useQuery / useRecord):
import { useQuery, useRecord } from "@kunkunsh/sdk/utils";
function List() {
const { data, isLoading, nextCursor, loadMore, revalidate } = useQuery<Bookmark>("bookmarks", {
where: [["tags", "contains", "dev"]],
orderBy: [["visits", "desc"]],
limit: 20,
});
// 任何 add/set/update/delete → 自动重新查询 → 重新渲染
}
function Detail({ id }: { id: string }) {
const { record, isLoading } = useRecord<Bookmark>("bookmarks", id);
}错误处理
import { db, DbError } from "@kunkunsh/sdk";
try {
await bookmarks.delete("nope");
} catch (e) {
if (e instanceof DbError && e.code === "RECORD_NOT_FOUND") { /* … */ }
}错误码:RECORD_NOT_FOUND、QUOTA_EXCEEDED、INVALID_QUERY、INVALID_COLLECTION_NAME、BATCH_TOO_LARGE、EMBEDDING_MODEL_NOT_BOUND。得益于 kkrpc 的错误恢复机制,instanceof DbError 和 .code 在你的扩展中正常工作。
同步与限制
当前的起始限制(可调整):
| 限制 | 值 |
|---|---|
| 记录文档大小 | ≤ 1 MB |
searchText 长度 | ≤ 16 KB |
| 每个插件总计 | ≤ 50 MB 软限制 |
| 批量操作 | ≤ 1000 条 |
查询 limit / where 子句 / in 值 | ≤ 500 / ≤ 10 / ≤ 100 |
该存储设计为以每条记录对象的形式接入 Kunkun 未来的云同步(scope_type: "record"),每条记录采用最后写入者胜出策略——这正是为什么 id 是 ULID 且写入是按行的。语义(向量)搜索是计划中的后续功能;目前请使用 FTS5 searchText。
集合管理
await db.listCollections(); // 获取此插件所有集合的元数据
await db.deleteCollection("bookmarks");