Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • Storage

Storage

OpenCode 使用键值存储系统来持久化数据。

概述

Storage 基于文件系统实现持久化,提供简单的键值 API。所有数据存储在 ~/.opencode/data/ 目录下。

存储位置

~/.opencode/data/
├── project/
│   └── <project-id>.json
├── session/
│   └── <project-id>/
│       ├── <session-id>.json
│       └── session_diff/
│           └── <session-id>.json
├── message/
│   └── <session-id>/
│       └── <message-id>.json
├── part/
│   └── <message-id>/
│       └── <part-id>.json
├── permission/
│   └── <project-id>.json
├── permission_approval/
│   └── <project-id>.json
├── permission_pending/
│   └── <project-id>/
│       └── <session-id>.json
└── share/
    └── <session-id>.json

API

读取

async function read<T>(key: string[]): Promise<T>

从存储中读取值。

示例:

const session = await Storage.read<Session.Info>(["session", projectID, sessionID])

写入

async function write<T>(key: string[], value: T): Promise<void>

写入值到存储。

示例:

await Storage.write(["session", projectID, sessionID], session)

更新

async function update<T>(key: string[], updater: (draft: T) => void): Promise<T>

读取、修改并写回值。

示例:

const updated = await Storage.update(["session", projectID, sessionID], (draft) => {
  draft.title = "New title"
  draft.time.updated = Date.now()
})

移除

async function remove(key: string[]): Promise<void>

从存储中删除值。

示例:

await Storage.remove(["session", projectID, sessionID])

列出

async function list(prefix: string[]): Promise<string[][]>

列出所有具有给定前缀的键。

示例:

const messages = await Storage.list(["message", sessionID])

for (const key of messages) {
  const messageID = key[key.length - 1]
  console.log(`Message: ${messageID}`)
}

存储键结构

核心对象

对象键模式
Project["project", projectID]
Session["session", projectID, sessionID]
Message["message", sessionID, messageID]
Part["part", messageID, partID]

元数据

对象键模式
SessionDiff["session_diff", sessionID]
Share["share", sessionID]
Permission["permission", projectID]

权限

对象键模式
Approval["permission_approval", projectID]
Pending["permission_pending", projectID, sessionID]

数据格式

所有存储的数据都是 JSON 格式。

项目示例

{
  "id": "abc123",
  "worktree": "/home/user/project",
  "vcs": "git",
  "time": {
    "created": 1640995200000,
    "updated": 1640995200000
  },
  "sandboxes": []
}

会话示例

{
  "id": "sess_abc123",
  "slug": "xyz789",
  "projectID": "abc123",
  "directory": "/home/user/project",
  "title": "New session",
  "version": "1.0.0",
  "time": {
    "created": 1640995200000,
    "updated": 1640995200000
  }
}

并发安全

Storage 使用文件锁确保并发安全:

// 内部实现
async function withLock<T>(key: string[], fn: () => Promise<T>): Promise<T> {
  const lockFile = getLockPath(key)

  // 等待锁
  await waitForLock(lockFile)

  try {
    // 执行操作
    return await fn()
  } finally {
    // 释放锁
    await releaseLock(lockFile)
  }
}

错误处理

文件不存在

try {
  const value = await Storage.read(["key"])
} catch (error) {
  if (error.code === "ENOENT") {
    // 文件不存在
  }
}

解析错误

try {
  const value = await Storage.read<T>(key)
} catch (error) {
  if (error.name === "ZodError") {
    // JSON 格式不匹配
  }
}

性能考虑

批量读取

// 读取多个消息
const messages = await Promise.all(messageIDs.map((id) => Storage.read(["message", sessionID, id])))

批量写入

// 写入多个 parts
await Promise.all(parts.map((part) => Storage.write(["part", part.messageID, part.id], part)))

数据迁移

当存储结构变更时,需要迁移旧数据:

async function migrateV1ToV2() {
  const projects = await Storage.list(["project"])

  for (const key of projects) {
    const project = await Storage.read(key)

    // 检查是否需要迁移
    if (!project.sandboxes) {
      // 添加新字段
      project.sandboxes = []

      // 写回
      await Storage.write(key, project)
    }
  }
}

清理策略

过期数据

// 清理 30 天前的会话
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000

const sessions = await Storage.list(["session", projectID])

for (const key of sessions) {
  const session = await Storage.read(key)

  if (session.time.created < cutoff) {
    // 删除过期会话
    await Session.remove(session.id)
  }
}

存储监控

使用事件

Bus.subscribeAll(async (event) => {
  console.log(`Event: ${event.type}`)
  console.log(`Data: ${JSON.stringify(event.properties)}`)
})

关系图

最佳实践

  1. 使用完整键路径: 避免使用通配符
  2. 批量操作: 使用 Promise.all 进行批量读写
  3. 错误处理: 总是处理 ENOENT 和解析错误
  4. 数据验证: 使用 Zod schema 验证读取的数据
  5. 性能: 避免频繁的小写入,考虑批量更新

变更历史

版本变更内容日期
v1初始 Storage 实现-
v1.1添加文件锁支持-
v1.2添加并发安全-

相关文档

  • Session - 会话对象
  • Message - 消息对象
  • Part - 消息部分