SuperAPI AI 工具集

ai-persistence/build-prisma-adapter

数据与分析

查看项目

更新于 10-16 09:42

技能介绍

用途

当应用已经在运行 Prisma、且需要 TanStack AI 的聊天持久化能力时使用本技能。交付物是应用中的一个文件 src/lib/chat-persistence.ts,它导出一个基于应用现有 PrismaClient 构建的 ChatPersistence;同时向应用现有 schema.prisma 添加四个模型,并使用应用自身的 prisma migrate 创建迁移。

明确不创建的内容:不创建 package、不创建第二个 client、不创建 datasource 块、不创建 generator、不手写 SQL 迁移。应用本身已有这些。

参考与一致性

需要阅读 Store Reference(docs/persistence/store-reference.md)以了解 store 契约与不变量,并阅读 ai-persistence/stores 了解形状规则。每个 store 都镜像 @tanstack/ai-persistence 中的参考内存后端(memory.ts);共享一致性测试套件是证明。

先阅读应用,再写任何东西

需要查找以下内容:schema 位置(prisma/schema.prisma,或多文件 prisma/schema/ 目录)决定是追加到现有文件还是新增一个 .prisma 文件;provider(datasource 块)决定 Json 是否可用;客户端单例(src/lib/prisma.ts、src/db.ts、globalThis 开发缓存)决定 chat-persistence.ts 的导入来源——绝不使用 new PrismaClient();生成的客户端(generator client 块的 output、prisma-client-js 与 prisma-client)决定 ChatRun/ChatInterrupt 行类型的来源;已有模型名决定 Message/Run 是否被占用,若被占用则加前缀;迁移流程(prisma/migrations/,或脚本中的 db push)决定使用 prisma migrate dev 还是 prisma db push。

Prisma 6 和 7 均可工作:delegate 查询 API(findUnique、upsert、update、findMany、delete)没有变化,因此应用生成的是哪个客户端并不重要。

切勿自创迁移路径。应先添加模型,然后让用户运行其自身的 npx prisma migrate dev --name chat-persistence(或 db push)以及 prisma generate。

向现有 schema 添加模型

ID 为 String,时间戳为 BigInt(可移植的 epoch 毫秒——Int 在 2038 年溢出,DateTime 会强制在每个边界转换),JSON payload 为 String。使用 @map/@@map 匹配应用的数据库命名。

四个模型:

ChatThread:threadId(String,@id,@map 为 thread_id)、messagesJson(String,@map 为 messages_json)、updatedAt(BigInt,@map 为 updated_at),@@map 为 chat_threads。

ChatRun:runId、threadId、status、startedAt(BigInt)、finishedAt(BigInt?)、error、errorCode、usageJson、sandboxKey、detachedSince(BigInt?)、cancelRequested(Boolean?)、driverEpoch(Int?)、parentRunId、subagentRunId、name;索引包括 [threadId, status]、[threadId, startedAt]、[parentRunId, startedAt](支撑 listByParentRun)、[status, detachedSince](支撑 listReclaimable);@@map 为 chat_runs。

ChatInterrupt:interruptId、runId、threadId、status、requestedAt(BigInt)、resolvedAt(BigInt?)、payloadJson、responseJson;索引 [threadId, requestedAt];@@map 为 chat_interrupts。

ChatMetadata:namespace、key、valueJson(@map 为 value_json),@@id([namespace, key]),@@map 为 chat_metadata。

模型可自由重命名以适配应用——下方的 store 代码是唯一引用它们的地方。额外的应用自有字段(如 userId、审计列)只要可选或带默认值即可,这样 store 的创建仍能成功。namespace 是 MetadataStore 的第一个参数;指南中的标准 SQL 将同一列称为 scope。

RunRecord.error 是结构化的 RunError({ message: string, code?: string }),因此使用两列而非一个 JSON blob:error 保存 provider 的说明文字,errorCode 保存稳定的分类,供运维过滤和分组。error 与 errorCode 在 update 中始终一起变动,因此后续无 code 的失败绝不会残留此前失败留下的陈旧 code。

在 Postgres 或 MySQL 上,可以把 *Json 字段改为 Prisma 的 Json 类型,并去掉下方映射器中的 JSON.stringify/parse。若应用面向 SQLite 或是多 provider,则保持 String。

编写 src/lib/chat-persistence.ts

SQL 后端需要两种转换:BigInt 时间戳的进出,以及 JSON 以字符串形式存储。其余都是共享不变量集合。

文件结构包括:导入 defineAIPersistence、@prisma/client 的 ChatInterrupt/ChatRun/Prisma/PrismaClient 类型、@tanstack/ai 的 ModelMessage/TokenUsage 类型、@tanstack/ai-persistence 的 ChatPersistence/InterruptRecord/InterruptStatus/InterruptStore/MessageStore/MetadataStore/RunRecord/RunStatus/RunStore 类型,以及 @/lib/prisma 的 prisma。

辅助函数:parseJson<T> 信任 store 自身写入的形状;RUN_STATUSES 为 ['running','interrupted','completed','failed','aborted'];INTERRUPT_STATUSES 为 ['pending','resolved','cancelled'];toRunStatus/toInterruptStatus 对普通 String 列做收窄校验,未知值抛错;mapRun/mapInterrupt 省略缺失的可选字段,以便与参考内存后端干净比较。

createMessageStore:loadThread 用 findUnique,未知 thread 返回 [] 而非 null;saveThread 是完整覆盖(messages 是完整的权威记录),使用 upsert 写入 messagesJson 与 BigInt(Date.now()) 的 updatedAt。

createRunStore:get 用 findUnique;createOrResume 用 upsert 且 update 为空(相当于 Prisma 的 ON CONFLICT DO NOTHING),已存在的 runId 原样返回,因此恢复与重复提交是安全的;update 构建 Prisma.ChatRunUpdateManyMutationInput,status、finishedAt、error(与 errorCode 一起写入,errorCode 为 null 表示无)、usage 分别处理;四个持久运行字段(sandboxKey、detachedSince、cancelRequested、driverEpoch)使用 'field' in patch 而非 !== undefined,因为重新附加会显式传入 undefined 来清除 detachedSince,这必须写为 NULL 而不能被静默丢弃(cancelRequested 同理,false 是有意义的值);若 data 为空则直接返回;否则用 updateMany。对未知 runId 打补丁是空操作,绝不抛错也绝不插入。可选方法:findActiveRun(threadId + status running,按 startedAt desc)、listByThread(按 startedAt asc,使用 (threadId, startedAt) 索引)、listByParentRun(按 startedAt asc,reconstructChat 用它恢复 subagent 卡片)、listReclaimable(status running 且 detachedSince 不为 null 且 <= cutoff,cutoff 为零包含,使用 (status, detachedSince) 索引)。

createInterruptStore:所有列表按 requestedAt 升序;create 用 upsert 实现 insert-if-absent,重复创建绝不把已解决的 interrupt 打回 pending;resolve 用 updateMany 设 status resolved、resolvedAt 与可选 responseJson;cancel 设 status cancelled 与 resolvedAt;get 用 findUnique;list、listPending、listByRun、listPendingByRun 基于共享的 listWhere。

createMetadataStore:get 用 findUnique 与复合唯一键 namespace_key;set 在 value 为 null 时抛出 TypeError(因为 JSON.stringify(undefined) 返回 undefined,会被 Prisma 以晦涩错误拒绝),并提示使用 delete() 清除元数据;set 用 upsert;delete 用 deleteMany。

最后导出 chatPersistence: ChatPersistence = defineAIPersistence,其 stores 包含 messages、runs、interrupts、metadata,分别由上述四个工厂以 prisma 构建。

局限与未提供的信息

资料未说明该技能是否收费、价格、支持的平台范围、性能指标或其他未提及的使用步骤。上述内容均依据所提供资料整理,缺失事实暂未提供。