Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3e0e477
feat(persistence): add ai-persistence + drizzle/prisma/cloudflare bac…
AlemTuzlak Jul 22, 2026
29382a9
feat(ai-client): browser-refresh durability via the persistence option
AlemTuzlak Jul 22, 2026
0ed55b8
example(ts-react-chat): persistent chat with SQLite + reload durability
AlemTuzlak Jul 22, 2026
01bc50e
docs(persistence): add persistence section for server + browser durab…
AlemTuzlak Jul 22, 2026
2a92c33
docs(skills): cover persistence in middleware + chat-experience skills
AlemTuzlak Jul 22, 2026
c33dc5d
test(e2e): persistence browser-refresh durability spec
AlemTuzlak Jul 22, 2026
b443a9f
ci: apply automated fixes
autofix-ci[bot] Jul 22, 2026
89932a8
feat(ai-client): message-storage lever for the persistence option
AlemTuzlak Jul 23, 2026
e8aef35
docs(persistence): explain durability + persistence as one story
AlemTuzlak Jul 23, 2026
a14adc6
feat(ai-client): zero-config storage adapters + threadId-keyed persis…
AlemTuzlak Jul 23, 2026
a5ca7d2
feat(ai-persistence): reconstructChat server helper
AlemTuzlak Jul 23, 2026
ac68bf7
docs(persistence): recommended setup, zero-config adapters, header-ac…
AlemTuzlak Jul 23, 2026
e5c4064
ci: apply automated fixes
autofix-ci[bot] Jul 23, 2026
8f408cb
docs(persistence): consolidate client persistence into one page
AlemTuzlak Jul 23, 2026
57aafee
docs(persistence): lead client-persistence with both use-cases
AlemTuzlak Jul 23, 2026
08d2ea3
fix(ai-client): keep host initialMessages in messages:false mode
AlemTuzlak Jul 23, 2026
cea0a83
ci: apply automated fixes
autofix-ci[bot] Jul 23, 2026
caf0990
feat(ai-persistence)!: rename withChatPersistence to withPersistence
AlemTuzlak Jul 23, 2026
f2eeba6
ci: apply automated fixes
autofix-ci[bot] Jul 23, 2026
ef3e5f5
feat(persistence): durable sandbox persistence via withPersistence
AlemTuzlak Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/client-browser-refresh-durability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@tanstack/ai-client': minor
'@tanstack/ai-react': minor
'@tanstack/ai-solid': minor
'@tanstack/ai-vue': minor
'@tanstack/ai-svelte': minor
'@tanstack/ai-angular': minor
'@tanstack/ai-preact': minor
---

Add browser-refresh durability to the `persistence` option.

The client `persistence` adapter now stores one combined record per chat id, the message transcript plus a resume snapshot, so a full page reload restores the conversation, rehydrates any pending interrupt, and rejoins a run that was still streaming (via `joinRun`, when the connection is durability-backed). A bare `UIMessage[]` from an older store is still read for backward compatibility.

The `persistence` option also accepts an object form, `{ store, messages?: boolean }`. `messages: false` caches only the tiny resume pointer (which run to rejoin, which interrupts are pending), keeping large transcripts off the client, durability rejoin and interrupt restore still work, and the server stays authoritative for history. A bare adapter is shorthand for `{ store, messages: true }`.

New web storage adapters are exported for this: `localStoragePersistence`, `sessionStoragePersistence`, and `indexedDBPersistence` (plus `StorageUnavailableError` and the `ChatPersistedState` / `ChatStorageAdapter` / `ChatPersistenceConfig` / `ChatPersistenceOption` types). Because durability rides the existing `persistence` option, every framework integration (`react`, `solid`, `vue`, `svelte`, `angular`, `preact`) gets it with no framework-specific code.
19 changes: 19 additions & 0 deletions .changeset/persistence-packages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@tanstack/ai-persistence': minor
'@tanstack/ai-persistence-drizzle': minor
'@tanstack/ai-persistence-prisma': minor
'@tanstack/ai-persistence-cloudflare': minor
---

Add server-side persistence for `chat()`: durable thread messages, run records, and interrupts.

`withPersistence(persistence)` is a chat middleware that stores the conversation transcript, tracks each run's status, and records interrupt state so a paused run (tool approval, client-tool execution, generic interrupt) survives a server restart. Point it at any backend that implements the store contract:

- `@tanstack/ai-persistence` — the store contracts, the `withPersistence` / `withGenerationPersistence` middleware, and an in-memory reference store (`memoryPersistence`) plus a conformance testkit.
- `@tanstack/ai-persistence-drizzle` — Drizzle-backed stores (SQLite via `node:sqlite`, plus a bring-your-own-schema contract) with migration and schema CLIs.
- `@tanstack/ai-persistence-prisma` — Prisma-backed stores with a models CLI that emits a provider-neutral schema fragment.
- `@tanstack/ai-persistence-cloudflare` — D1-backed stores (delegating to the Drizzle backend) plus a Durable-Object lock store for cross-instance locking.

Resume reconstruction is delegated to the chat engine: persistence records interrupts and gates new input on a thread with pending interrupts, while the engine rebuilds the resume tool state from the resume batch and the interrupt bindings carried in the (server-loaded) message history.

`reconstructChat(persistence, request)` is a server helper that returns a thread's stored messages as a JSON `Response`, so a server-authoritative client can hydrate its transcript on load from a one-line `GET` handler.
18 changes: 18 additions & 0 deletions .changeset/sandbox-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@tanstack/ai': minor
'@tanstack/ai-sandbox': minor
'@tanstack/ai-persistence': minor
'@tanstack/ai-persistence-drizzle': minor
'@tanstack/ai-persistence-prisma': minor
'@tanstack/ai-persistence-cloudflare': minor
---

Add durable **sandbox persistence**: cross-process / multi-instance resume for `@tanstack/ai-sandbox`, provided by the same `withPersistence` used for chat.

`withSandbox` consumes `SandboxStore` (which sandbox to resume) and `LockStore` (mutual exclusion around ensure) as optional capabilities, defaulting to in-memory (single-process). This makes them durable without a sandbox-specific middleware:

- `withPersistence` now provides the `SandboxStoreCapability` (and the shared `LocksCapability`) whenever its store set includes them. Compose `[withPersistence(persistence), withSandbox(sandbox)]`.
- `AIPersistenceStores` gains an optional `sandbox?: SandboxStore`. The backends carry it out of the box: `sqlitePersistence` / `drizzlePersistence(db)` (new `sandboxes` table in the shipped schema + migration), `prismaPersistence(prisma)` (new `Sandbox` model; the delegate resolves lazily so chat-only clients without it keep working), and `cloudflarePersistence({ d1 })` (D1, delegating to Drizzle). `memoryPersistence()` includes an in-memory sandbox store.
- The Cloudflare Durable-Object lock (`durableObjects`) doubles as the distributed sandbox lock.

**Shared tokens in core.** `SandboxStore` / `SandboxRecord` / `SandboxStoreCapability` / `InMemorySandboxStore` and the `LockStore` / `LocksCapability` / `InMemoryLockStore` primitives now live in core `@tanstack/ai` (their neutral home). `@tanstack/ai-sandbox` and `@tanstack/ai-persistence` re-export them, so one shared token reference lets a persistence-provided store and lock reach `withSandbox` with no dependency between the two packages. A `SandboxStore` conformance testkit is exported from `@tanstack/ai-sandbox/testkit` (`runSandboxStoreConformance`); every backend runs it.
153 changes: 0 additions & 153 deletions docs/chat/persistence.md

This file was deleted.

81 changes: 73 additions & 8 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,6 @@
"label": "Thinking & Reasoning",
"to": "chat/thinking-content",
"addedAt": "2026-04-15"
},
{
"label": "Persistence",
"to": "chat/persistence",
"addedAt": "2026-06-02",
"updatedAt": "2026-07-14"
}
]
},
Expand Down Expand Up @@ -224,7 +218,8 @@
{
"label": "Overview",
"to": "resumable-streams/overview",
"addedAt": "2026-07-17"
"addedAt": "2026-07-17",
"updatedAt": "2026-07-23"
},
{
"label": "Advanced",
Expand All @@ -238,6 +233,71 @@
}
]
},
{
"label": "Persistence",
"children": [
{
"label": "Overview",
"to": "persistence/overview",
"addedAt": "2026-07-22",
"updatedAt": "2026-07-23"
},
{
"label": "Chat Persistence",
"to": "persistence/chat-persistence",
"addedAt": "2026-07-22"
},
{
"label": "Client Persistence",
"to": "persistence/client-persistence",
"addedAt": "2026-07-22",
"updatedAt": "2026-07-23"
},
{
"label": "Controls",
"to": "persistence/controls",
"addedAt": "2026-07-22",
"updatedAt": "2026-07-23"
},
{
"label": "Custom Stores",
"to": "persistence/custom-stores",
"addedAt": "2026-07-22"
},
{
"label": "SQL Backends",
"to": "persistence/sql-backends",
"addedAt": "2026-07-22",
"updatedAt": "2026-07-23"
},
{
"label": "Drizzle",
"to": "persistence/drizzle",
"addedAt": "2026-07-22"
},
{
"label": "Prisma",
"to": "persistence/prisma",
"addedAt": "2026-07-22"
},
{
"label": "Cloudflare",
"to": "persistence/cloudflare",
"addedAt": "2026-07-22",
"updatedAt": "2026-07-23"
},
{
"label": "Migrations",
"to": "persistence/migrations",
"addedAt": "2026-07-22"
},
{
"label": "Internals",
"to": "persistence/internals",
"addedAt": "2026-07-22"
}
]
},
{
"label": "Protocol",
"children": [
Expand Down Expand Up @@ -396,7 +456,7 @@
"label": "Overview",
"to": "sandbox/overview",
"addedAt": "2026-06-16",
"updatedAt": "2026-07-03"
"updatedAt": "2026-07-23"
},
{
"label": "Quick Start",
Expand Down Expand Up @@ -441,6 +501,11 @@
"addedAt": "2026-06-29",
"updatedAt": "2026-07-09"
},
{
"label": "Persistence",
"to": "sandbox/persistence",
"addedAt": "2026-07-23"
},
{
"label": "Events",
"to": "sandbox/events",
Expand Down
Loading