# L9 插件存储(SQLite) > 每个插件一块**私有**持久化空间:一个 SQLite 库 + 一个自由文件目录。 > KV 简单状态、SQL 结构化数据、文件随便放;schema 必须机器可读声明——它是 Agent 自我迭代与 UI 展示的同一份事实源。 ## 1. 空间布局 ``` userData/plugin-data// data.db ← SQLite 库(WAL 模式):kv 表(宿主管理)+ 插件自建表 data.db-wal ← WAL 伴生文件 <其余自由文件> ← api.storage.dir() 指向这里,插件自行读写 ``` - **每插件一库**(不是共享大库):导出/清除/损坏恢复都以插件为单位,跨插件天然隔离。 - 卸载/移除插件**不删数据**(重装即恢复);删除只在设置页点「清除数据」(二次确认)时发生。 ## 2. API(全部 async——底层在宿主共享 worker 线程执行,永不阻塞主进程) ```ts export default definePlugin(async (api) => { // ① 声明机器可读 schema(使用 db 前必须声明;plugin:check 强制) api.storage.declareSchema({ kv: { description: '会话级偏好:ui. = 用户界面偏好' }, tables: [ { name: 'notes', description: '用户笔记', columns: [ { name: 'id', type: 'TEXT', description: '主键,ulid' }, { name: 'title', type: 'TEXT', description: '标题,非空' }, { name: 'created_at', type: 'INTEGER', description: '创建时间,Unix 毫秒' } ] } ] }) // ② KV:宿主管理的 kv 表(value 走 JSON 编码,适合设置类小状态) const last = await api.storage.kv.get('ui.lastQuery') await api.storage.kv.set('ui.lastQuery', 'postgres') await api.storage.kv.delete('ui.lastQuery') const keys = await api.storage.kv.keys() // ③ 自建表:SQL 自由发挥 await api.storage.db.exec('CREATE TABLE IF NOT EXISTS notes (...)') // DDL,激活期执行 await api.storage.db.run('INSERT INTO notes (id, title) VALUES (?, ?)', ['n1', '标题']) const rows = await api.storage.db.query('SELECT * FROM notes WHERE title LIKE ?', ['%标%']) // 原子批量:worker 端 BEGIN/COMMIT,任一失败整体回滚 await api.storage.db.batch([ { sql: 'INSERT INTO notes (id, title) VALUES (?, ?)', params: ['n2', 'a'] }, { sql: 'UPDATE notes SET title = ? WHERE id = ?', params: ['b', 'n2'] } ]) // ④ 自由文件(日志快照、导出等) const dir = api.storage.dir() // userData/plugin-data// }) ``` ## 3. schema 声明为什么是硬性要求 插件会被 **Agent 自我迭代**。迭代前 Agent 需要知道「库里有什么」: - `declareSchema` 的内容随 `plugins:list` 下发——管理页「存储区」卡片与(未来的)Agent 内省工具读的是**同一份**数据; - 每张表、每个字段的 `description` 必填,`plugin:check` 会拒绝「用了 db 却不声明 schema」的插件; - 写给 Agent 读的 description 请具体(「创建时间,Unix 毫秒」而不是「时间」)。 ## 4. 迁移(表结构变更) 宿主不解释 schema,迁移由插件自管,惯用法: ```ts const row = await api.storage.db.query('PRAGMA user_version') const v = Number(row[0]?.user_version ?? 0) if (v < 1) { await api.storage.db.exec('ALTER TABLE notes ADD COLUMN tag TEXT') await api.storage.db.exec('PRAGMA user_version = 1') } ``` ## 5. 设置页管理 - 二级页「存储区」卡片:schema 表格化展示(表/列/类型/描述)+ **数据目录**(资源管理器打开)+ **清除数据**(二次确认,连接关闭后整目录删除)。 ## 6. 边界 - 存储只在 **main 层**提供;renderer 组件 / iframe 页需要数据时,让 main 侧经状态胶囊/设置值转发(受控 IPC 通道属后续契约)。 - KV 的 value 必须可 JSON 序列化(Date/Map 请先转换);大二进制放文件,路径存 KV。 - 跨插件**不直读对方库**:需要别人的数据 = 声明依赖 + 走对方服务(见 L8)。