ai-persistence/build-prisma-adapter
Use when an app already runs Prisma and needs TanStack AI chat persistence — writes a chat-persistence.ts into the app against its existing PrismaClient and schema.prisma. Covers the four models, BigInt timestamps, JSON-as-string columns, upsert-with-empty-update idempotency, and model renaming.
git clone --depth 1 https://github.com/TanStack/ai /tmp/ai-persistence-build-prisma-adapter && cp -r /tmp/ai-persistence-build-prisma-adapter/packages/ai-persistence/skills/ai-persistence/build-prisma-adapter ~/.claude/skills/ai-persistence-build-prisma-adapterSKILL.md
# Prisma Chat Persistence
The deliverable is **one file in the app** — `src/lib/chat-persistence.ts` —
exporting a `ChatPersistence` built from the app's existing `PrismaClient`. Plus
four models added to the app's existing `schema.prisma` and a migration created
with the app's own `prisma migrate`.
Do not create a package, a second client, a datasource block, a generator, or a
hand-written SQL migration. The app has those.
Read the **Store Reference**
(`docs/persistence/store-reference.md`) for the store contracts and
invariants, and **ai-persistence/stores** for the shape rules. Every
store below mirrors the reference in-memory backend in
`@tanstack/ai-persistence` (`memory.ts`); the shared conformance testkit is the
proof.
## 1. Read the app before writing anything
| Find | Where to look | What it decides |
| -------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| Schema location | `prisma/schema.prisma`, or a multi-file `prisma/schema/` dir | Append to the existing file, or add one new `.prisma` file |
| Provider | the `datasource` block | Whether `Json` is available; nothing else changes |
| Client singleton | `src/lib/prisma.ts`, `src/db.ts`, `globalThis` dev cache | What `chat-persistence.ts` imports — never `new PrismaClient()` |
| Generated client | the `generator client` block (`output`, `prisma-client-js` vs `prisma-client`) | Where `ChatRun`/`ChatInterrupt` row types come from |
| Existing model names | the schema | Whether `Message`/`Run` are taken — prefix if so |
| Migration flow | `prisma/migrations/`, or `db push` in scripts | `prisma migrate dev` vs `prisma db push` |
Prisma 6 and 7 both work: the delegate query API (`findUnique`, `upsert`,
`update`, `findMany`, `delete`) is unchanged, so it does not matter which
client the app generated.
**Never invent a migration path.** Add the models, then have the user run their
own `npx prisma migrate dev --name chat-persistence` (or `db push`) and
`prisma generate`.
## 2. Add the models to their schema
IDs are `String`, timestamps are `BigInt` (portable epoch ms — `Int` overflows
in 2038, `DateTime` forces a conversion at every boundary), JSON payloads are
`String`. Use `@map`/`@@map` to match the app's database naming.
```prisma
model ChatThread {
threadId String @id @map("thread_id")
messagesJson String @map("messages_json")
updatedAt BigInt @map("updated_at")
@@map("chat_threads")
}
model ChatRun {
runId String @id @map("run_id")
threadId String @map("thread_id")
status String
startedAt BigInt @map("started_at")
finishedAt BigInt? @map("finished_at")
error String?
errorCode String? @map("error_code")
usageJson String? @map("usage_json")
sandboxKey String? @map("sandbox_key")
detachedSince BigInt? @map("detached_since")
cancelRequested Boolean? @map("cancel_requested")
driverEpoch Int? @map("driver_epoch")
@@index([threadId, status])
@@index([threadId, startedAt])
// Powers listReclaimable: status = 'running' AND detachedSince <= cutoff.
@@index([status, detachedSince])
@@map("chat_runs")
}
model ChatInterrupt {
interruptId String @id @map("interrupt_id")
runId String @map("run_id")
threadId String @map("thread_id")
status String
requestedAt BigInt @map("requested_at")
resolvedAt BigInt? @map("resolved_at")
payloadJson String @map("payload_json")
responseJson String? @map("response_json")
@@index([threadId, requestedAt])
@@map("chat_interrupts")
}
model ChatMetadata {
namespace String
key String
valueJson String @map("value_json")
@@id([namespace, key])
@@map("chat_metadata")
}
```
Rename models freely to fit the app — the store code below is the only thing
that references them. Extra app-owned fields (a `userId`, audit columns) are
fine as long as they are optional or defaulted, so the stores' creates still
succeed. `namespace` is the `MetadataStore` first argument; the stock SQL in
the guide calls the same column `scope`.
`RunRecord.error` is a structured `RunError` (`{ message: string, code?: string }`),
so it gets two columns rather than one JSON blob: `error` for the provider's
prose and `errorCode` for the stable classification an operator filters and
groups by. `error` and `errorCode` always move together in `update`, so a
later code-less failure can never leave a stale `code` from an earlier one
behind.
On **Postgres or MySQL** you can switch the `*Json` fields to Prisma's `Json`
type and drop the `JSON.stringify`/`parse` in the mappers below. Keep `String`
if the app targets SQLite or if it is multi-provider.
## 3. Write `src/lib/chat-persistence.ts`
Two conversions the SQL backends do not need: `BigInt` timestamps in and out,
and JSON as strings. Everything else is the shared invariant set.
```ts ignore
import { defineAIPersistence } from '@tanstack/ai-persistence'
import type {
ChatInterrupt,
ChatRun,
Prisma,
PrismaClient,
} from '@prisma/client'
import type { ModelMessage, TokenUsage } from '@tanstack/ai'
import type {
ChatPersistence,
InterruptRecord,
InterruptStatus,
InterruptStore,
MessageStore,
MetadataStore,
RunRecord,
RunStatus,
RunStore,
} from '@tanstack/ai-persistence'
import { prisma } from '@/lib/prisma'
// Trusts the shape the stores themselves wrote — nothing else writes these
// columns.
function parseJson<T>(raw: string): T {
retur>
Triage all open GitHub issues, PRs, and discussions in the current repository by fanning out up to 100 parallel subagents (one per item), then produce a single prioritized report ranking which PRs to review first, which issues to address first, and which discussions need maintainer attention. Use when the user asks to "triage open issues/PRs", "triage discussions", "prioritize the backlog", "what should I review first", "sweep the repo", or any request to bulk-evaluate open GitHub work and recommend an order.
>
>
>
>
>
>