import PouchDB from 'pouchdb-core'; import { RemoteRevCache, ProcessedFilesCache, SyncSeqCache, } from '../types.js'; import { logLocalCache } from '../utils/logger.js'; /** * 本地缓存管理器 * * rev2 设计用 PouchDB 的 _local 文档存放三份缓存,避免污染目标文件系统、 * 不参与 replication、且在 Node / 浏览器环境下均可工作。 * * - remote-rev-cache:记录目标文件系统每个文档当前的 _rev,用于 push 差异筛选 * - processed-files:记录已写入文件的内容哈希,用于 pull 时跳过未变文件 * - sync-seq:上次 push 的 update_seq,用于轻量跳过 */ export class LocalCache { constructor( private db: PouchDB.Database, private basePath: string ) {} private remoteRevDocId(): string { return `_local/sync-remote-rev:${this.basePath}`; } private processedFilesDocId(): string { return `_local/sync-processed-files:${this.basePath}`; } private syncSeqDocId(): string { return `_local/sync-seq:${this.basePath}`; } // ---------- remote-rev-cache ---------- async getRemoteRevCache(): Promise { const doc = await this.readLocal(this.remoteRevDocId()); return doc ?? { basePath: this.basePath, revs: {} }; } async setRemoteRevCache(cache: RemoteRevCache): Promise { logLocalCache.log('[local-cache] setRemoteRevCache: %d 个 rev', Object.keys(cache.revs).length); await this.writeLocal(this.remoteRevDocId(), cache); } // ---------- processed-files ---------- async getProcessedFiles(): Promise { const doc = await this.readLocal(this.processedFilesDocId()); return doc ?? { basePath: this.basePath, hashes: {} }; } async setProcessedFiles(cache: ProcessedFilesCache): Promise { logLocalCache.log('[local-cache] setProcessedFiles: %d 个文件哈希', Object.keys(cache.hashes).length); await this.writeLocal(this.processedFilesDocId(), cache); } // ---------- sync-seq ---------- async getSyncSeq(): Promise { const doc = await this.readLocal(this.syncSeqDocId()); return doc ?? { basePath: this.basePath, lastPushedSeq: null }; } async setSyncSeq(cache: SyncSeqCache): Promise { logLocalCache.log('[local-cache] setSyncSeq: lastPushedSeq=%s', cache.lastPushedSeq); await this.writeLocal(this.syncSeqDocId(), cache); } // ---------- 底层读写 ---------- private async readLocal(docId: string): Promise { try { return (await this.db.get(docId)) as T; } catch (err: any) { if (err.status === 404) return null; throw err; } } private async writeLocal(docId: string, doc: T): Promise { let existing: (T & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) | null = null; try { existing = await this.db.get(docId); } catch { existing = null; } if (existing) { await this.db.put({ ...doc, _id: docId, _rev: existing._rev }); } else { await this.db.put({ ...doc, _id: docId }); } } }