@tanstack/ai-persistence 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source
|
|
4
|
+
media="(prefers-color-scheme: dark)"
|
|
5
|
+
srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
|
|
6
|
+
/>
|
|
7
|
+
<source
|
|
8
|
+
media="(prefers-color-scheme: light)"
|
|
9
|
+
srcset="https://tanstack.com/api/readme/ai.png"
|
|
10
|
+
/>
|
|
11
|
+
<img
|
|
12
|
+
src="https://tanstack.com/api/readme/ai.png"
|
|
13
|
+
alt="TanStack AI"
|
|
14
|
+
width="900"
|
|
15
|
+
/>
|
|
16
|
+
</picture>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<br />
|
|
20
|
+
|
|
21
|
+
# @tanstack/ai-persistence
|
|
22
|
+
|
|
23
|
+
Composable state persistence for TanStack AI messages, runs, interrupts, metadata, and locks
|
|
24
|
+
|
|
25
|
+
A conversation that only lives in memory is gone on reload and absent on a second device. This package stores it in your own database: one middleware on the server writes the transcript — and, with the matching stores configured, run status and pending approvals — through an adapter you define, and the client asks the server for the thread on mount. The client half ships in the framework package you already use (`@tanstack/ai-react`, `-vue`, `-solid`, `-svelte`, `-angular`, or `@tanstack/ai-client`).
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @tanstack/ai-persistence
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @tanstack/ai-persistence
|
|
33
|
+
# or
|
|
34
|
+
yarn add @tanstack/ai-persistence
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Server: store the conversation
|
|
40
|
+
|
|
41
|
+
`withPersistence` writes the transcript into your `messages` store, plus run status and pending approvals when the adapter also provides `runs` and `interrupts`. Start with `memoryPersistence()` for local development and swap in your own adapter later:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import {
|
|
45
|
+
chat,
|
|
46
|
+
chatParamsFromRequest,
|
|
47
|
+
toServerSentEventsResponse,
|
|
48
|
+
} from '@tanstack/ai'
|
|
49
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
50
|
+
import { withPersistence } from '@tanstack/ai-persistence'
|
|
51
|
+
import { persistence } from './persistence'
|
|
52
|
+
|
|
53
|
+
export async function POST(request: Request) {
|
|
54
|
+
const params = await chatParamsFromRequest(request)
|
|
55
|
+
const stream = chat({
|
|
56
|
+
adapter: openaiText('gpt-5.5'),
|
|
57
|
+
messages: params.messages,
|
|
58
|
+
threadId: params.threadId,
|
|
59
|
+
runId: params.runId,
|
|
60
|
+
...(params.resume ? { resume: params.resume } : {}),
|
|
61
|
+
middleware: [withPersistence(persistence)],
|
|
62
|
+
})
|
|
63
|
+
return toServerSentEventsResponse(stream)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Client: bring it back
|
|
68
|
+
|
|
69
|
+
`persistence: true` puts the server in charge: the browser caches nothing and fetches the thread on mount. Best for multi-user and multi-device apps.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
|
|
73
|
+
|
|
74
|
+
function Chat() {
|
|
75
|
+
const { messages, sendMessage } = useChat({
|
|
76
|
+
threadId: 'support-chat',
|
|
77
|
+
connection: fetchServerSentEvents('/api/chat'),
|
|
78
|
+
persistence: true,
|
|
79
|
+
})
|
|
80
|
+
return <button onClick={() => sendMessage('hi')}>{messages.length}</button>
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Prefer the browser to own the history? Pass a storage adapter instead — `localStoragePersistence()`, `sessionStoragePersistence()`, or `indexedDBPersistence()` — and no server store is needed.
|
|
85
|
+
|
|
86
|
+
### Survive a reload mid-answer
|
|
87
|
+
|
|
88
|
+
Add a `GET` on the same route. `reconstructChat` returns the stored thread plus a cursor to any run still generating; `useChat` tails that run so the reply finishes in place.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { chatParamsFromRequest } from '@tanstack/ai'
|
|
92
|
+
import { reconstructChat } from '@tanstack/ai-persistence'
|
|
93
|
+
import { persistence } from './persistence'
|
|
94
|
+
|
|
95
|
+
export function GET(request: Request) {
|
|
96
|
+
return reconstructChat(persistence, request, {
|
|
97
|
+
// WITHOUT this, anyone who guesses a thread id gets the whole transcript.
|
|
98
|
+
authorize: async (threadId, req) => ownsThread(req, threadId),
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Build your own adapter
|
|
104
|
+
|
|
105
|
+
An adapter is a plain object of store functions. The core never looks at your tables, so the schema stays yours. One store, `messages`, is enough for `withPersistence`:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import {
|
|
109
|
+
defineAIPersistence,
|
|
110
|
+
defineMessageStore,
|
|
111
|
+
} from '@tanstack/ai-persistence'
|
|
112
|
+
import { db } from './db'
|
|
113
|
+
|
|
114
|
+
export const persistence = defineAIPersistence({
|
|
115
|
+
stores: {
|
|
116
|
+
messages: defineMessageStore({
|
|
117
|
+
// Return [] for a thread that was never saved, never null.
|
|
118
|
+
loadThread: (threadId) => db.threads.messages(threadId),
|
|
119
|
+
// The full transcript, not a delta. Overwrite what you had.
|
|
120
|
+
saveThread: (threadId, messages) => db.threads.save(threadId, messages),
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The other stores are optional and add capabilities as you provide them: `runs`, `interrupts`, and `metadata` for chat state; `generationRuns`, `artifacts`, and `blobs` for image, video, speech, and transcription runs. `composePersistence` layers overrides on top of a base backend.
|
|
127
|
+
|
|
128
|
+
### Prove it with the conformance suite
|
|
129
|
+
|
|
130
|
+
The same suite every packaged backend runs is shipped for yours:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { runPersistenceConformance } from '@tanstack/ai-persistence/testkit'
|
|
134
|
+
import { sqlitePersistence } from './sqlite-persistence'
|
|
135
|
+
|
|
136
|
+
runPersistenceConformance('my sqlite adapter', () =>
|
|
137
|
+
sqlitePersistence({ url: ':memory:', migrate: true }),
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Stores you do not provide go in `skip`. The testkit is a Vitest suite; `vitest` is an optional peer dependency, so install it in your project before importing `@tanstack/ai-persistence/testkit`.
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
- [Overview](https://tanstack.com/ai/latest/docs/persistence/overview): the three steps, and which setup you want
|
|
146
|
+
- [Chat persistence](https://tanstack.com/ai/latest/docs/persistence/chat-persistence): the server middleware in full, including durable interrupts
|
|
147
|
+
- [Client persistence](https://tanstack.com/ai/latest/docs/persistence/client-persistence): the modes, storage backends, and what a reload restores
|
|
148
|
+
- [Build your own adapter](https://tanstack.com/ai/latest/docs/persistence/build-your-own-adapter)
|
|
149
|
+
- [Controls](https://tanstack.com/ai/latest/docs/persistence/controls): compose backends per store
|
|
150
|
+
- [Store reference](https://tanstack.com/ai/latest/docs/persistence/store-reference): every store's methods and how the records relate
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-persistence",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Composable state persistence for TanStack AI messages, runs, interrupts, metadata, and locks.",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"skills"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@tanstack/ai-utils": "^0.4.
|
|
42
|
+
"@tanstack/ai-utils": "^0.4.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"vitest": "^4.1.10",
|