Files
frontend/src/services/database.ts

936 lines
29 KiB
TypeScript
Raw Normal View History

/**
*
* 使 SQLite
* 使
*/
import * as SQLite from 'expo-sqlite';
import type {
UserDTO,
GroupResponse,
GroupMemberResponse,
ConversationResponse,
ConversationDetailResponse,
} from '../types/dto';
// 数据库实例
let db: SQLite.SQLiteDatabase | null = null;
let writeQueue: Promise<void> = Promise.resolve();
// 当前数据库对应的用户ID
let currentDbUserId: string | null = null;
// 数据库版本,用于迁移
const DB_VERSION = 2;
/**
*
* @param userId ID
*/
export const initDatabase = async (userId?: string): Promise<void> => {
try {
// 如果指定了用户ID使用用户专属数据库
// 否则使用默认数据库(兼容旧版本)
const dbName = userId ? `carrot_bbs_${userId}.db` : 'carrot_bbs.db';
// 如果存在旧的数据库连接且用户ID变化需要关闭旧连接
if (db && currentDbUserId !== userId) {
console.log(`[Database] 切换用户数据库: ${currentDbUserId} -> ${userId}`);
await closeDatabase();
}
// 如果已经打开了正确的数据库,直接返回
if (db && currentDbUserId === userId) {
console.log(`[Database] 数据库已初始化: ${dbName}`);
return;
}
db = await SQLite.openDatabaseAsync(dbName);
currentDbUserId = userId || null;
console.log(`[Database] 打开数据库: ${dbName}`);
// 创建消息表(包含新字段 seq, status, segments
await db.execAsync(`
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY NOT NULL,
conversationId TEXT NOT NULL,
senderId TEXT NOT NULL,
content TEXT,
type TEXT DEFAULT 'text',
isRead INTEGER DEFAULT 0,
createdAt TEXT NOT NULL,
seq INTEGER DEFAULT 0,
status TEXT DEFAULT 'normal',
segments TEXT
);
`);
// 创建会话表
await db.execAsync(`
CREATE TABLE IF NOT EXISTS conversations (
id TEXT PRIMARY KEY NOT NULL,
participantId TEXT NOT NULL,
lastMessageId TEXT,
lastSeq INTEGER DEFAULT 0,
unreadCount INTEGER DEFAULT 0,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
`);
// 会话缓存表(完整 JSON兼容私聊/群聊不同结构)
await db.execAsync(`
CREATE TABLE IF NOT EXISTS conversation_cache (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
`);
// 会话列表缓存表(仅用于 ChatList避免被详情缓存污染
await db.execAsync(`
CREATE TABLE IF NOT EXISTS conversation_list_cache (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
`);
// 用户缓存表
await db.execAsync(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
`);
// 当前登录用户缓存(仅保存 me
await db.execAsync(`
CREATE TABLE IF NOT EXISTS current_user_cache (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
`);
// 群组缓存表
await db.execAsync(`
CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
);
`);
// 群成员缓存表
await db.execAsync(`
CREATE TABLE IF NOT EXISTS group_members (
groupId TEXT NOT NULL,
userId TEXT NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL,
PRIMARY KEY (groupId, userId)
);
`);
// 创建索引
await db.execAsync(`
CREATE INDEX IF NOT EXISTS idx_messages_conversationId ON messages(conversationId);
`);
await db.execAsync(`
CREATE INDEX IF NOT EXISTS idx_group_members_groupId ON group_members(groupId);
`);
// 迁移:检查并添加新列(兼容旧版本数据库)
await migrateDatabase();
// 迁移后再创建依赖新列的索引
await db.execAsync(`
CREATE INDEX IF NOT EXISTS idx_messages_seq ON messages(seq);
`);
await db.execAsync(`
CREATE INDEX IF NOT EXISTS idx_messages_conversation_seq ON messages(conversationId, seq);
`);
console.log('数据库初始化成功');
} catch (error) {
console.error('数据库初始化失败:', error);
throw error;
}
};
/**
*
*/
export const closeDatabase = async (): Promise<void> => {
if (db) {
try {
await db.closeAsync();
console.log('[Database] 数据库连接已关闭');
} catch (error) {
console.error('[Database] 关闭数据库失败:', error);
}
db = null;
currentDbUserId = null;
}
};
/**
* ID
*/
export const getCurrentDbUserId = (): string | null => {
return currentDbUserId;
};
// 数据库迁移
const migrateDatabase = async (): Promise<void> => {
const database = await getDb();
try {
// 检查 messages 表是否有 seq 列
const tableInfo = await database.getAllAsync<any>(
`PRAGMA table_info(messages)`
);
const columns = tableInfo.map(col => col.name);
// 添加 seq 列
if (!columns.includes('seq')) {
await database.execAsync(`ALTER TABLE messages ADD COLUMN seq INTEGER DEFAULT 0`);
console.log('数据库迁移:添加 seq 列');
}
// 添加 status 列
if (!columns.includes('status')) {
await database.execAsync(`ALTER TABLE messages ADD COLUMN status TEXT DEFAULT 'normal'`);
console.log('数据库迁移:添加 status 列');
}
// 添加 segments 列(用于存储消息的 segments JSON
if (!columns.includes('segments')) {
await database.execAsync(`ALTER TABLE messages ADD COLUMN segments TEXT`);
console.log('数据库迁移:添加 segments 列');
}
// 检查 conversations 表是否有 lastSeq 列
const convTableInfo = await database.getAllAsync<any>(
`PRAGMA table_info(conversations)`
);
const convColumns = convTableInfo.map(col => col.name);
if (!convColumns.includes('lastSeq')) {
await database.execAsync(`ALTER TABLE conversations ADD COLUMN lastSeq INTEGER DEFAULT 0`);
console.log('数据库迁移:添加 lastSeq 列');
}
} catch (error) {
console.error('数据库迁移失败:', error);
}
};
// 获取数据库实例
const getDb = async (): Promise<SQLite.SQLiteDatabase> => {
if (!db) {
throw new Error('数据库未初始化,请先调用 initDatabase(userId)');
}
return db;
};
const isRecoverableDbError = (error: unknown): boolean => {
const message = String(error);
return (
message.includes('NativeDatabase.prepareAsync') ||
message.includes('NullPointerException')
);
};
const withDbRead = async <T>(operation: (database: SQLite.SQLiteDatabase) => Promise<T>): Promise<T> => {
let database = await getDb();
try {
return await operation(database);
} catch (error) {
if (!isRecoverableDbError(error)) {
throw error;
}
console.error('数据库读取异常,尝试重连后重试:', error);
db = null;
database = await getDb();
return operation(database);
}
};
const enqueueWrite = async <T>(operation: () => Promise<T>): Promise<T> => {
const wrappedOperation = async () => {
try {
return await operation();
} catch (error) {
if (!isRecoverableDbError(error)) {
throw error;
}
console.error('数据库写入异常,尝试重连后重试:', error);
db = null;
return operation();
}
};
const queued = writeQueue.then(wrappedOperation, wrappedOperation);
writeQueue = queued.then(() => undefined, () => undefined);
return queued;
};
// ==================== 消息类型定义 ====================
export interface CachedMessage {
id: string;
conversationId: string;
senderId: string;
content?: string;
type?: string;
isRead: boolean;
createdAt: string;
seq: number;
status: string;
segments?: any;
}
// ==================== 消息操作 ====================
// 保存单条消息
export const saveMessage = async (message: CachedMessage): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`INSERT OR REPLACE INTO messages (id, conversationId, senderId, content, type, isRead, createdAt, seq, status, segments)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
message.id,
message.conversationId,
message.senderId,
message.content || '',
message.type || 'text',
message.isRead ? 1 : 0,
message.createdAt,
message.seq || 0,
message.status || 'normal',
message.segments ? JSON.stringify(message.segments) : null,
]
);
});
};
// 批量保存消息
export const saveMessagesBatch = async (messages: CachedMessage[]): Promise<void> => {
if (!messages || messages.length === 0) return;
await enqueueWrite(async () => {
const database = await getDb();
for (const message of messages) {
await database.runAsync(
`INSERT OR REPLACE INTO messages (id, conversationId, senderId, content, type, isRead, createdAt, seq, status, segments)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
message.id,
message.conversationId,
message.senderId,
message.content || '',
message.type || 'text',
message.isRead ? 1 : 0,
message.createdAt,
message.seq || 0,
message.status || 'normal',
message.segments ? JSON.stringify(message.segments) : null,
]
);
}
});
};
// 获取会话的消息默认只加载最近的20条避免卡顿
export const getMessagesByConversation = async (
conversationId: string,
limit: number = 20
): Promise<CachedMessage[]> => {
return withDbRead(async (database) => {
const result = await database.getAllAsync<any>(
`SELECT * FROM messages
WHERE conversationId = ?
ORDER BY seq DESC
LIMIT ?`,
[conversationId, limit]
);
// 反转以保持时间正序(最新的在底部)
return result.reverse().map(msg => ({
...msg,
isRead: msg.isRead === 1,
segments: msg.segments ? JSON.parse(msg.segments) : undefined,
}));
});
};
// 获取会话的最大消息序号
export const getMaxSeq = async (conversationId: string): Promise<number> => {
return withDbRead(async (database) => {
const result = await database.getFirstAsync<{ maxSeq: number }>(
`SELECT MAX(seq) as maxSeq FROM messages WHERE conversationId = ?`,
[conversationId]
);
return result?.maxSeq || 0;
});
};
// 获取会话的最小消息序号
export const getMinSeq = async (conversationId: string): Promise<number> => {
return withDbRead(async (database) => {
const result = await database.getFirstAsync<{ minSeq: number }>(
`SELECT MIN(seq) as minSeq FROM messages WHERE conversationId = ?`,
[conversationId]
);
return result?.minSeq || 0;
});
};
// 获取指定 seq 之前的消息(用于加载历史)
export const getMessagesBeforeSeq = async (conversationId: string, beforeSeq: number, limit: number = 20): Promise<CachedMessage[]> => {
return withDbRead(async (database) => {
const result = await database.getAllAsync<any>(
`SELECT * FROM messages
WHERE conversationId = ? AND seq < ?
ORDER BY seq DESC
LIMIT ?`,
[conversationId, beforeSeq, limit]
);
return result.map(msg => ({
...msg,
isRead: msg.isRead === 1,
segments: msg.segments ? JSON.parse(msg.segments) : undefined,
})).reverse(); // 反转以保持时间正序
});
};
// 获取本地消息数量(用于判断是否还有更多历史)
export const getLocalMessageCountBeforeSeq = async (conversationId: string, beforeSeq: number): Promise<number> => {
const database = await getDb();
const result = await database.getFirstAsync<{ count: number }>(
`SELECT COUNT(*) as count FROM messages WHERE conversationId = ? AND seq < ?`,
[conversationId, beforeSeq]
);
return result?.count || 0;
};
// 获取消息数量
export const getMessageCount = async (conversationId: string): Promise<number> => {
const database = await getDb();
const result = await database.getFirstAsync<{ count: number }>(
`SELECT COUNT(*) as count FROM messages WHERE conversationId = ?`,
[conversationId]
);
return result?.count || 0;
};
// 标记消息为已读
export const markMessageAsRead = async (messageId: string): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`UPDATE messages SET isRead = 1 WHERE id = ?`,
[messageId]
);
});
};
// 标记会话的所有消息为已读
export const markConversationAsRead = async (conversationId: string): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`UPDATE messages SET isRead = 1 WHERE conversationId = ?`,
[conversationId]
);
});
};
// 删除单条消息
export const deleteMessage = async (messageId: string): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`DELETE FROM messages WHERE id = ?`,
[messageId]
);
});
};
// 更新消息状态(如撤回)
export const updateMessageStatus = async (messageId: string, status: string): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`UPDATE messages SET status = ? WHERE id = ?`,
[status, messageId]
);
});
};
// 清空会话的所有消息
export const clearConversationMessages = async (conversationId: string): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`DELETE FROM messages WHERE conversationId = ?`,
[conversationId]
);
});
};
// ==================== 会话操作 ====================
// 保存或更新会话
export const saveConversation = async (conversation: {
id: string;
participantId: string;
lastMessageId?: string;
lastSeq?: number;
unreadCount?: number;
createdAt: string;
updatedAt: string;
}): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`INSERT OR REPLACE INTO conversations (id, participantId, lastMessageId, lastSeq, unreadCount, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
conversation.id,
conversation.participantId,
conversation.lastMessageId || null,
conversation.lastSeq || 0,
conversation.unreadCount || 0,
conversation.createdAt,
conversation.updatedAt,
]
);
});
};
// 获取所有会话
export const getAllConversations = async (): Promise<any[]> => {
const database = await getDb();
const result = await database.getAllAsync<any>(
`SELECT * FROM conversations ORDER BY updatedAt DESC`
);
return result;
};
// 更新会话未读数
export const updateConversationUnreadCount = async (conversationId: string, count: number): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`UPDATE conversations SET unreadCount = ? WHERE id = ?`,
[count, conversationId]
);
});
};
// 更新会话最后消息
export const updateConversationLastMessage = async (
conversationId: string,
lastMessageId: string,
lastSeq: number,
updatedAt: string
): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`UPDATE conversations SET lastMessageId = ?, lastSeq = ?, updatedAt = ? WHERE id = ?`,
[lastMessageId, lastSeq, updatedAt, conversationId]
);
});
};
// 删除会话(包含所有消息)
export const deleteConversation = async (conversationId: string): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`DELETE FROM messages WHERE conversationId = ?`,
[conversationId]
);
await database.runAsync(
`DELETE FROM conversations WHERE id = ?`,
[conversationId]
);
});
};
// ==================== 搜索 ====================
// 搜索消息
export const searchMessages = async (keyword: string): Promise<CachedMessage[]> => {
const database = await getDb();
const result = await database.getAllAsync<any>(
`SELECT * FROM messages
WHERE content LIKE ?
ORDER BY createdAt DESC
LIMIT 50`,
[`%${keyword}%`]
);
return result.map(msg => ({
...msg,
isRead: msg.isRead === 1,
segments: msg.segments ? JSON.parse(msg.segments) : undefined,
}));
};
// ==================== 工具函数 ====================
// 获取数据库统计信息
export const getDatabaseStats = async (): Promise<{
totalMessages: number;
totalConversations: number;
}> => {
const database = await getDb();
const msgCount = await database.getFirstAsync<{ count: number }>(
`SELECT COUNT(*) as count FROM messages`
);
const convCount = await database.getFirstAsync<{ count: number }>(
`SELECT COUNT(*) as count FROM conversations`
);
return {
totalMessages: msgCount?.count || 0,
totalConversations: convCount?.count || 0,
};
};
// 清空所有数据(用于调试或用户退出登录)
export const clearAllData = async (): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.execAsync(`DELETE FROM messages`);
await database.execAsync(`DELETE FROM conversations`);
await database.execAsync(`DELETE FROM conversation_cache`);
await database.execAsync(`DELETE FROM conversation_list_cache`);
await database.execAsync(`DELETE FROM users`);
await database.execAsync(`DELETE FROM current_user_cache`);
await database.execAsync(`DELETE FROM groups`);
await database.execAsync(`DELETE FROM group_members`);
});
};
// ==================== 通用 JSON 缓存辅助 ====================
const safeParseJson = <T>(value: string): T | null => {
try {
return JSON.parse(value) as T;
} catch {
return null;
}
};
// ==================== 用户缓存 ====================
export const saveUserCache = async (user: UserDTO): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`INSERT OR REPLACE INTO users (id, data, updatedAt) VALUES (?, ?, ?)`,
[String(user.id), JSON.stringify(user), new Date().toISOString()]
);
});
};
export const saveUsersCache = async (users: UserDTO[]): Promise<void> => {
if (!users || users.length === 0) return;
await enqueueWrite(async () => {
const database = await getDb();
for (const user of users) {
await database.runAsync(
`INSERT OR REPLACE INTO users (id, data, updatedAt) VALUES (?, ?, ?)`,
[String(user.id), JSON.stringify(user), new Date().toISOString()]
);
}
});
};
export const getUserCache = async (userId: string): Promise<UserDTO | null> => {
return withDbRead(async (database) => {
const row = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM users WHERE id = ?`,
[String(userId)]
);
if (!row?.data) return null;
return safeParseJson<UserDTO>(row.data);
});
};
export const getLatestUserCache = async (): Promise<UserDTO | null> => {
const database = await getDb();
const row = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM users ORDER BY updatedAt DESC LIMIT 1`
);
if (!row?.data) return null;
return safeParseJson<UserDTO>(row.data);
};
export const saveCurrentUserCache = async (user: UserDTO): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`INSERT OR REPLACE INTO current_user_cache (id, data, updatedAt) VALUES (?, ?, ?)`,
['me', JSON.stringify(user), new Date().toISOString()]
);
});
};
export const getCurrentUserCache = async (): Promise<UserDTO | null> => {
return withDbRead(async (database) => {
const row = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM current_user_cache WHERE id = 'me'`
);
if (!row?.data) return null;
return safeParseJson<UserDTO>(row.data);
});
};
export const clearCurrentUserCache = async (): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(`DELETE FROM current_user_cache WHERE id = 'me'`);
});
};
// ==================== 群组缓存 ====================
export const saveGroupCache = async (group: GroupResponse): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
await database.runAsync(
`INSERT OR REPLACE INTO groups (id, data, updatedAt) VALUES (?, ?, ?)`,
[String(group.id), JSON.stringify(group), new Date().toISOString()]
);
});
};
export const saveGroupsCache = async (groups: GroupResponse[]): Promise<void> => {
if (!groups || groups.length === 0) return;
await enqueueWrite(async () => {
const database = await getDb();
for (const group of groups) {
await database.runAsync(
`INSERT OR REPLACE INTO groups (id, data, updatedAt) VALUES (?, ?, ?)`,
[String(group.id), JSON.stringify(group), new Date().toISOString()]
);
}
});
};
export const getGroupCache = async (groupId: string): Promise<GroupResponse | null> => {
return withDbRead(async (database) => {
const row = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM groups WHERE id = ?`,
[String(groupId)]
);
if (!row?.data) return null;
return safeParseJson<GroupResponse>(row.data);
});
};
export const getAllGroupsCache = async (): Promise<GroupResponse[]> => {
return withDbRead(async (database) => {
const rows = await database.getAllAsync<{ data: string }>(
`SELECT data FROM groups ORDER BY updatedAt DESC`
);
return rows
.map(row => safeParseJson<GroupResponse>(row.data))
.filter((item): item is GroupResponse => Boolean(item));
});
};
// ==================== 群成员缓存 ====================
export const saveGroupMembersCache = async (
groupId: string,
members: GroupMemberResponse[]
): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
const now = new Date().toISOString();
await database.runAsync(`DELETE FROM group_members WHERE groupId = ?`, [String(groupId)]);
for (const member of members) {
await database.runAsync(
`INSERT OR REPLACE INTO group_members (groupId, userId, data, updatedAt) VALUES (?, ?, ?, ?)`,
[String(groupId), String(member.user_id), JSON.stringify(member), now]
);
}
});
};
export const getGroupMembersCache = async (groupId: string): Promise<GroupMemberResponse[]> => {
return withDbRead(async (database) => {
const rows = await database.getAllAsync<{ data: string }>(
`SELECT data FROM group_members WHERE groupId = ? ORDER BY updatedAt DESC`,
[String(groupId)]
);
return rows
.map(row => safeParseJson<GroupMemberResponse>(row.data))
.filter((item): item is GroupMemberResponse => Boolean(item));
});
};
// ==================== 会话缓存 ====================
type ConversationCacheData = ConversationResponse | ConversationDetailResponse;
export const saveConversationCache = async (conversation: ConversationCacheData): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
const cacheUpdatedAt = ('updated_at' in conversation && conversation.updated_at)
? String(conversation.updated_at)
: new Date().toISOString();
await database.runAsync(
`INSERT OR REPLACE INTO conversation_cache (id, data, updatedAt) VALUES (?, ?, ?)`,
[String(conversation.id), JSON.stringify(conversation), cacheUpdatedAt]
);
});
};
export const saveConversationsCache = async (conversations: ConversationResponse[]): Promise<void> => {
if (!conversations || conversations.length === 0) return;
await enqueueWrite(async () => {
const database = await getDb();
for (const conversation of conversations) {
await database.runAsync(
`INSERT OR REPLACE INTO conversation_list_cache (id, data, updatedAt) VALUES (?, ?, ?)`,
[
String(conversation.id),
JSON.stringify(conversation),
conversation.updated_at || new Date().toISOString(),
]
);
}
});
};
export const getConversationCache = async (conversationId: string): Promise<ConversationCacheData | null> => {
return withDbRead(async (database) => {
const row = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM conversation_cache WHERE id = ?`,
[String(conversationId)]
);
if (!row?.data) return null;
return safeParseJson<ConversationCacheData>(row.data);
});
};
export const getConversationListCache = async (): Promise<ConversationResponse[]> => {
return withDbRead(async (database) => {
let rows = await database.getAllAsync<{ data: string }>(
`SELECT data FROM conversation_list_cache ORDER BY updatedAt DESC`
);
// 兼容旧版本:首次升级时尝试从旧表读取一次
if (rows.length === 0) {
rows = await database.getAllAsync<{ data: string }>(
`SELECT data FROM conversation_cache ORDER BY updatedAt DESC`
);
}
const list = rows
.map(row => safeParseJson<ConversationResponse>(row.data))
.filter((item): item is ConversationResponse => Boolean(item))
.filter(item => typeof item.id !== 'undefined' && typeof item.type !== 'undefined');
return list.sort((a, b) => {
const aPinned = a.is_pinned ? 1 : 0;
const bPinned = b.is_pinned ? 1 : 0;
if (aPinned !== bPinned) {
return bPinned - aPinned;
}
const aTime = new Date(a.last_message_at || a.updated_at || 0).getTime();
const bTime = new Date(b.last_message_at || b.updated_at || 0).getTime();
return bTime - aTime;
});
});
};
/**
* conversation_list_cache last_messagelast_message_atunread_count
* SWR
*/
export const updateConversationListCacheEntry = async (
conversationId: string,
updates: Partial<ConversationResponse>
): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
const row = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM conversation_list_cache WHERE id = ?`,
[String(conversationId)]
);
if (!row?.data) return;
const conv = safeParseJson<ConversationResponse>(row.data);
if (!conv) return;
const updated = { ...conv, ...updates };
const updatedAt = updates.last_message_at || updated.last_message_at || new Date().toISOString();
await database.runAsync(
`UPDATE conversation_list_cache SET data = ?, updatedAt = ? WHERE id = ?`,
[JSON.stringify(updated), updatedAt, String(conversationId)]
);
});
};
/**
* SWR
*/
export const updateConversationCacheUnreadCount = async (
conversationId: string,
count: number
): Promise<void> => {
await enqueueWrite(async () => {
const database = await getDb();
// 更新 conversation_list_cache
const listRow = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM conversation_list_cache WHERE id = ?`,
[String(conversationId)]
);
if (listRow?.data) {
const conv = safeParseJson<ConversationResponse>(listRow.data);
if (conv) {
conv.unread_count = count;
await database.runAsync(
`UPDATE conversation_list_cache SET data = ? WHERE id = ?`,
[JSON.stringify(conv), String(conversationId)]
);
}
}
// 更新 conversation_cache
const cacheRow = await database.getFirstAsync<{ data: string }>(
`SELECT data FROM conversation_cache WHERE id = ?`,
[String(conversationId)]
);
if (cacheRow?.data) {
const conv = safeParseJson<Record<string, unknown>>(cacheRow.data);
if (conv) {
conv.unread_count = count;
await database.runAsync(
`UPDATE conversation_cache SET data = ? WHERE id = ?`,
[JSON.stringify(conv), String(conversationId)]
);
}
}
});
};