/** * LocalMessage 模型(存储层副本) * * 与 `src/data/models/chat/chat_message.ts` 的 `ChatMessage` 解耦: * 存储层维护自己的数据形态(独立 id/role/content/createdAt + 内部 sessionId 分组), * 不耦合 wire 模型,方便后续 schema 独立演进。 * * 对齐项目内其他 Zod 模型的范式: * - `*Schema` 是验证与默认值的单一来源 * - `*Input` 是构造输入(字段可缺),`*Data` 是解析后形态 * - 不可变(private constructor + Object.freeze) * - 桥接方法 `toRow` / `fromRow` 处理 Dexie 行 ↔ 类实例的转换 */ import { z } from "zod"; import { ChatImageSchema, ChatLockDetailSchema } from "@/data/schemas/chat"; import type { LocalMessageRow } from "./local_chat_db"; export const LocalMessageSchema = z.object({ id: z.string(), role: z.string(), type: z.string().default("text"), content: z.string(), createdAt: z.string().default(""), audioUrl: z.string().nullable().default(null), image: ChatImageSchema, lockDetail: ChatLockDetailSchema, sessionId: z.string().default(""), }); export type LocalMessageInput = z.input; export type LocalMessageData = z.output; export class LocalMessage { declare readonly id: string; declare readonly role: string; declare readonly type: string; declare readonly content: string; declare readonly createdAt: string; declare readonly audioUrl: string | null; declare readonly image: z.output; declare readonly lockDetail: z.output; declare readonly sessionId: string; private constructor(input: LocalMessageInput) { const data = LocalMessageSchema.parse(input); Object.assign(this, data); Object.freeze(this); } static from(input: LocalMessageInput): LocalMessage { return new LocalMessage(input); } static fromJson(json: unknown): LocalMessage { return LocalMessage.from(json as LocalMessageInput); } toJson(): LocalMessageData { return LocalMessageSchema.parse(this); } /** 转成 Dexie 行(不含 dbId,让 Dexie 自增)。 */ toRow(): Omit { return { id: this.id, role: this.role, type: this.type, content: this.content, createdAt: this.createdAt, audioUrl: this.audioUrl, image: this.image, lockDetail: this.lockDetail, sessionId: this.sessionId, }; } /** 从 Dexie 行还原(忽略 dbId 内部主键)。 */ static fromRow(row: LocalMessageRow): LocalMessage { return LocalMessage.from({ id: row.id, role: row.role, type: row.type, content: row.content, createdAt: row.createdAt, audioUrl: row.audioUrl ?? null, image: row.image, lockDetail: row.lockDetail, sessionId: row.sessionId, }); } }