Skip to content

OPFS 存储适配器:可配置切换的持久化后端,CAS 语义与 IndexedDB 一致 (TML-90) - #5

Open
SeasonsChange422 wants to merge 1 commit into
TML-53from
agent/agent/8721038d
Open

OPFS 存储适配器:可配置切换的持久化后端,CAS 语义与 IndexedDB 一致 (TML-90)#5
SeasonsChange422 wants to merge 1 commit into
TML-53from
agent/agent/8721038d

Conversation

@SeasonsChange422

Copy link
Copy Markdown
Member

摘要

TML-53 范围项 TML-90:OPFS 存储适配器。实现与现有 IndexedDB 适配器可切换的 OPFS(Origin Private File System)持久化后端,经持久化门面 project-persistence.ts 统一接入;行为与 IndexedDB 完全一致:项目保存/加载/删除/最近项目 + revision 防倒退(CAS) 语义,既有持久化功能(TML-53 AC1-AC4)不回归。

实现

  • packages/studio/src/persistence/project-storage.ts(新)— 共享存储抽象:StorageBackend'indexeddb' | 'opfs')、ProjectStorage 接口、SaveOutcome/RenameOutcome/DuplicateOutcomeestimateStorage、CAS 比对与配额错误判定等公共工具。
  • packages/studio/src/persistence/project-store-opfs.ts(新)— OpfsProjectStore
    • 存储结构 <root>/projects/<encodeURIComponent(uri)>,记录与 IndexedDB 同形;根目录名同为 lumora-studio,切换后端不混淆命名空间;
    • 并发安全对齐 IndexedDB 事务:save 临界区(读-比-写)在互斥锁内执行 —— 优先 Web Locks(跨标签页互斥),不可用时进程内 promise 链退化(同标签页串行);写入为「临时文件落盘 + move 原子覆盖」,配额不足/中断时旧记录保持原样;
    • CAS 语义逐项对齐:number = 期望基线比对(落后基线拒绝、追平基线拒绝、同 revision 幂等重存允许)、null = 创建语义(已存在拒绝)、undefined = 无条件写入;
    • 损坏记录策略(OPFS 特有):load 视为缺失、save 拒绝覆盖(storage-error)、list 跳过、remove 可删除(修复路径);
    • OPFS 不可用时 create 返回 null,持久化静默降级(与 IndexedDB 一致)。
  • project-persistence.ts / studio-runtime.ts / LumoraStudio.tsxinit({ storage: 'opfs' | 'indexeddb' }) 可配置后端,缺省 'indexeddb'(既有行为不变);新增 persistence.backend 只读属性。
  • examples/embedded-host/src/App.tsx — 经 ?storage=opfs 查询参数切换后端(演示宿主)。
  • index.ts — 导出 OpfsProjectStore 与存储抽象类型。

测试

  • 单测:project-store-opfs.test.ts(14,内存 OPFS shim,覆盖重开持久化、列表排序、CAS 全部语义、跨实例并发、损坏记录、配额原子性与临时文件清理、OPFS 不可用降级);project-persistence-opfs.test.ts(6,门面层切换、后端隔离、AC2 冲突锁存与「加载较新版本」解决)。
  • e2e:e2e/persistence-opfs.spec.ts(真实 Chromium OPFS;AC1 导出→清空→导入→刷新重开全链路 + 直接断言数据落 OPFS 目录;AC2 双标签页 Web Locks 下 CAS 冲突与显式解决)。

验证(本机)

  • 单元测试 693 通过 / 1 跳过(vitest 全量)
  • typecheck、lint(0 错误)、build 全部通过
  • e2e 57/57 通过(npm run e2e;曾出现 4 个 WebGL/视口密集用例在整包并发下超时,单独重跑与整包重跑均通过,判定为资源争用性 flake,非回归)

- 新增共享存储抽象 project-storage.ts(StorageBackend/ProjectStorage/SaveOutcome 等)
- 新增 OpfsProjectStore:Web Locks 跨标签页互斥 + 临时文件 move 原子写,
  CAS(期望基线/创建语义/无条件)与损坏记录策略对齐 IndexedDB 行为
- project-persistence/studio-runtime/LumoraStudio 支持 init({ storage: 'opfs' }),
  嵌入式宿主经 ?storage=opfs 切换;缺省仍为 IndexedDB,既有功能不回归
- 单测:project-store-opfs(14)+ project-persistence-opfs(6),内存 OPFS shim
- e2e:?storage=opfs 下 AC1 导出→清空→导入恢复与 AC2 跨标签页 CAS 冲突

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant