"use client"; /** * LocalChatStorage 完整实现(示范 2:IndexedDB via Dexie) * * 对齐 Dart 端 `LocalChatStorage`(lib/data/services/storage/chat/local_chat_storage.dart): * - init / saveMessage / saveMessages / getAllMessages / clearAll / deleteMessage / close * - 额外提供 `getMessageCount`(异步)替代 Dart 的同步 `messageCount` getter * - 额外提供 `getAllMessagesBySession(sessionId)`(filter 模拟,等下一轮加索引后再用 where) * * 单例挂在 class 静态字段。 * Dexie 实例可注入,便于测试用 fake-indexeddb 跑真 Dexie。 * * 注意:当前表无次级索引,所有过滤(sessionId)都是 `toArray().then(filter)`,O(n)。 * 数据量大时考虑升级 schema 加索引。 */ import { Result, type Result as ResultT } from "@/utils"; import { LocalChatDB } from "./local_chat_db"; import { LocalMessage } from "./local_message"; export class LocalChatStorage { private readonly db: LocalChatDB; private static _instance: LocalChatStorage | null = null; constructor(db?: LocalChatDB) { this.db = db ?? new LocalChatDB(); } static getInstance(): LocalChatStorage { if (!LocalChatStorage._instance) { LocalChatStorage._instance = new LocalChatStorage(); } return LocalChatStorage._instance; } /** @internal */ static _resetForTests(): void { LocalChatStorage._instance = null; } /** * 触发 Dexie 打开数据库。 * 幂等:Dexie.open() 多次调用无副作用。 * 在 bootstrap 中 await 此方法可保证后续操作不会因连接未开而延迟。 */ async init(): Promise> { try { await this.db.open(); return Result.ok(undefined); } catch (e) { return Result.err(e); } } // ---- create / append ---- async saveMessage(message: LocalMessage): Promise> { try { await this.db.messages.add(message.toRow()); return Result.ok(undefined); } catch (e) { return Result.err(e); } } async saveMessages(messages: readonly LocalMessage[]): Promise> { try { await this.db.transaction( "rw", this.db.messages, async () => { for (const m of messages) { await this.db.messages.add(m.toRow()); } }, ); return Result.ok(undefined); } catch (e) { return Result.err(e); } } // ---- read ---- async getAllMessages(): Promise> { try { const rows = await this.db.messages.toArray(); // 按 dbId 升序(与 Dart `box.values.toList()` 插入序语义一致) rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0)); return Result.ok(rows.map((r) => LocalMessage.fromRow(r))); } catch (e) { return Result.err(e); } } async getMessageCount(): Promise> { try { return Result.ok(await this.db.messages.count()); } catch (e) { return Result.err(e); } } async getAllMessagesBySession( sessionId: string, ): Promise> { try { const rows = await this.db.messages .filter((r) => r.sessionId === sessionId) .toArray(); rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0)); return Result.ok(rows.map((r) => LocalMessage.fromRow(r))); } catch (e) { return Result.err(e); } } // ---- delete ---- async clearAll(): Promise> { try { await this.db.messages.clear(); return Result.ok(undefined); } catch (e) { return Result.err(e); } } /** * 按数组下标删除(保持 Dart 端语义:`box.values.toList()[index].delete()`)。 * O(n) 取下标后单条 delete,与 Dart 行为一致。 */ async deleteMessage(index: number): Promise> { try { if (!Number.isInteger(index) || index < 0) { return Result.err( new RangeError(`deleteMessage: index ${index} out of range`), ); } const rows = await this.db.messages.toArray(); if (index >= rows.length) { return Result.err( new RangeError(`deleteMessage: index ${index} out of range`), ); } const target = rows[index]!; await this.db.messages.delete(target.dbId!); return Result.ok(undefined); } catch (e) { return Result.err(e); } } async close(): Promise> { try { this.db.close(); return Result.ok(undefined); } catch (e) { return Result.err(e); } } }