@smicolon/ai-kit 0.0.1 → 0.1.1
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/.claude-plugin/CLAUDE.md +7 -0
- package/.claude-plugin/marketplace.json +373 -0
- package/README.md +132 -0
- package/package.json +13 -3
- package/packs/architect/CHANGELOG.md +17 -0
- package/packs/architect/README.md +58 -0
- package/packs/architect/agents/system-architect.md +768 -0
- package/packs/architect/commands/diagram-create.md +300 -0
- package/packs/better-auth/.claude-plugin/plugin.json +14 -0
- package/packs/better-auth/.mcp.json +14 -0
- package/packs/better-auth/CHANGELOG.md +26 -0
- package/packs/better-auth/README.md +125 -0
- package/packs/better-auth/agents/auth-architect.md +278 -0
- package/packs/better-auth/commands/auth-provider-add.md +265 -0
- package/packs/better-auth/commands/auth-setup.md +298 -0
- package/packs/better-auth/skills/auth-security/SKILL.md +425 -0
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +455 -0
- package/packs/dev-loop/.claude-plugin/plugin.json +10 -0
- package/packs/dev-loop/CHANGELOG.md +69 -0
- package/packs/dev-loop/README.md +155 -0
- package/packs/dev-loop/commands/cancel-dev.md +21 -0
- package/packs/dev-loop/commands/dev-loop.md +72 -0
- package/packs/dev-loop/commands/dev-plan.md +351 -0
- package/packs/dev-loop/hooks/hooks.json +15 -0
- package/packs/dev-loop/hooks/stop-hook.sh +178 -0
- package/packs/dev-loop/scripts/setup-dev-loop.sh +194 -0
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +249 -0
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +874 -0
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +260 -0
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +275 -0
- package/packs/django/CHANGELOG.md +39 -0
- package/packs/django/README.md +92 -0
- package/packs/django/agents/django-architect.md +182 -0
- package/packs/django/agents/django-builder.md +250 -0
- package/packs/django/agents/django-feature-based.md +420 -0
- package/packs/django/agents/django-reviewer.md +253 -0
- package/packs/django/agents/django-tester.md +230 -0
- package/packs/django/commands/api-endpoint.md +285 -0
- package/packs/django/commands/model-create.md +178 -0
- package/packs/django/commands/test-generate.md +325 -0
- package/packs/django/rules/migrations.md +138 -0
- package/packs/django/rules/models.md +167 -0
- package/packs/django/rules/serializers.md +126 -0
- package/packs/django/rules/services.md +131 -0
- package/packs/django/rules/tests.md +140 -0
- package/packs/django/rules/views.md +102 -0
- package/packs/django/skills/import-convention-enforcer/SKILL.md +226 -0
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +343 -0
- package/packs/django/skills/migration-safety-checker/SKILL.md +375 -0
- package/packs/django/skills/model-entity-validator/SKILL.md +298 -0
- package/packs/django/skills/performance-optimizer/SKILL.md +447 -0
- package/packs/django/skills/red-phase-verifier/SKILL.md +180 -0
- package/packs/django/skills/security-first-validator/SKILL.md +435 -0
- package/packs/django/skills/test-coverage-advisor/SKILL.md +394 -0
- package/packs/django/skills/test-validity-checker/SKILL.md +194 -0
- package/packs/failure-log/.claude-plugin/plugin.json +14 -0
- package/packs/failure-log/CHANGELOG.md +20 -0
- package/packs/failure-log/README.md +168 -0
- package/packs/failure-log/commands/failure-add.md +106 -0
- package/packs/failure-log/commands/failure-list.md +89 -0
- package/packs/failure-log/hooks/hooks.json +16 -0
- package/packs/failure-log/hooks/scripts/inject-failures.sh +64 -0
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +164 -0
- package/packs/flutter/.claude-plugin/plugin.json +10 -0
- package/packs/flutter/CHANGELOG.md +19 -0
- package/packs/flutter/README.md +170 -0
- package/packs/flutter/agents/flutter-architect.md +166 -0
- package/packs/flutter/agents/flutter-builder.md +303 -0
- package/packs/flutter/agents/release-manager.md +355 -0
- package/packs/flutter/commands/fastlane-setup.md +188 -0
- package/packs/flutter/commands/flutter-build.md +90 -0
- package/packs/flutter/commands/flutter-deploy.md +133 -0
- package/packs/flutter/commands/flutter-test.md +117 -0
- package/packs/flutter/commands/signing-setup.md +209 -0
- package/packs/flutter/hooks/hooks.json +17 -0
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +193 -0
- package/packs/flutter/skills/flutter-architecture/SKILL.md +127 -0
- package/packs/flutter/skills/store-publishing/SKILL.md +163 -0
- package/packs/hono/.claude-plugin/plugin.json +19 -0
- package/packs/hono/CHANGELOG.md +19 -0
- package/packs/hono/README.md +143 -0
- package/packs/hono/agents/hono-architect.md +240 -0
- package/packs/hono/agents/hono-builder.md +285 -0
- package/packs/hono/agents/hono-reviewer.md +279 -0
- package/packs/hono/agents/hono-tester.md +346 -0
- package/packs/hono/commands/middleware-create.md +223 -0
- package/packs/hono/commands/project-init.md +306 -0
- package/packs/hono/commands/route-create.md +153 -0
- package/packs/hono/commands/rpc-client.md +263 -0
- package/packs/hono/hooks/hooks.json +4 -0
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +408 -0
- package/packs/hono/skills/hono-patterns/SKILL.md +309 -0
- package/packs/hono/skills/rpc-typesafe/SKILL.md +388 -0
- package/packs/hono/skills/zod-validation/SKILL.md +332 -0
- package/packs/nestjs/CHANGELOG.md +29 -0
- package/packs/nestjs/README.md +75 -0
- package/packs/nestjs/agents/nestjs-architect.md +402 -0
- package/packs/nestjs/agents/nestjs-builder.md +301 -0
- package/packs/nestjs/agents/nestjs-tester.md +437 -0
- package/packs/nestjs/commands/module-create.md +369 -0
- package/packs/nestjs/rules/controllers.md +92 -0
- package/packs/nestjs/rules/dto.md +124 -0
- package/packs/nestjs/rules/entities.md +102 -0
- package/packs/nestjs/rules/services.md +106 -0
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +389 -0
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +365 -0
- package/packs/nextjs/CHANGELOG.md +36 -0
- package/packs/nextjs/README.md +76 -0
- package/packs/nextjs/agents/frontend-tester.md +680 -0
- package/packs/nextjs/agents/frontend-visual.md +820 -0
- package/packs/nextjs/agents/nextjs-architect.md +331 -0
- package/packs/nextjs/agents/nextjs-modular.md +433 -0
- package/packs/nextjs/commands/component-create.md +398 -0
- package/packs/nextjs/rules/api-routes.md +129 -0
- package/packs/nextjs/rules/components.md +106 -0
- package/packs/nextjs/rules/hooks.md +132 -0
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +445 -0
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +399 -0
- package/packs/nextjs/skills/react-form-validator/SKILL.md +569 -0
- package/packs/nuxtjs/CHANGELOG.md +30 -0
- package/packs/nuxtjs/README.md +56 -0
- package/packs/nuxtjs/agents/frontend-tester.md +680 -0
- package/packs/nuxtjs/agents/frontend-visual.md +820 -0
- package/packs/nuxtjs/agents/nuxtjs-architect.md +537 -0
- package/packs/nuxtjs/commands/component-create.md +223 -0
- package/packs/nuxtjs/rules/components.md +101 -0
- package/packs/nuxtjs/rules/composables.md +118 -0
- package/packs/nuxtjs/rules/server-routes.md +127 -0
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +183 -0
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +196 -0
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +190 -0
- package/packs/onboard/CHANGELOG.md +22 -0
- package/packs/onboard/README.md +103 -0
- package/packs/onboard/agents/onboard-guide.md +118 -0
- package/packs/onboard/commands/onboard.md +313 -0
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +98 -0
- package/packs/tanstack-router/.claude-plugin/plugin.json +14 -0
- package/packs/tanstack-router/CHANGELOG.md +30 -0
- package/packs/tanstack-router/README.md +113 -0
- package/packs/tanstack-router/agents/tanstack-architect.md +173 -0
- package/packs/tanstack-router/agents/tanstack-builder.md +360 -0
- package/packs/tanstack-router/agents/tanstack-tester.md +454 -0
- package/packs/tanstack-router/commands/form-create.md +313 -0
- package/packs/tanstack-router/commands/query-create.md +263 -0
- package/packs/tanstack-router/commands/route-create.md +190 -0
- package/packs/tanstack-router/commands/table-create.md +413 -0
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +370 -0
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +346 -0
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +415 -0
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +425 -0
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +341 -0
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +359 -0
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +285 -0
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +351 -0
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +531 -0
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +428 -0
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +490 -0
- package/packs/worktree/.claude-plugin/plugin.json +19 -0
- package/packs/worktree/CHANGELOG.md +24 -0
- package/packs/worktree/README.md +110 -0
- package/packs/worktree/commands/wt.md +73 -0
- package/packs/worktree/scripts/wt.sh +396 -0
- package/packs/worktree/skills/worktree-manager/SKILL.md +68 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: TanStack AI Patterns (Alpha)
|
|
3
|
+
description: >-
|
|
4
|
+
TanStack AI patterns for unified AI SDK integration. Activates when
|
|
5
|
+
implementing AI chat, streaming responses, or AI-powered features.
|
|
6
|
+
NOTE: Alpha library - API may change.
|
|
7
|
+
version: 1.0.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TanStack AI Patterns (Alpha)
|
|
11
|
+
|
|
12
|
+
> **Alpha Library**: TanStack AI is in alpha. APIs may change between versions.
|
|
13
|
+
|
|
14
|
+
TanStack AI provides a unified SDK for integrating AI capabilities into React applications.
|
|
15
|
+
|
|
16
|
+
## Core Concepts
|
|
17
|
+
|
|
18
|
+
- **Providers**: Backend AI providers (OpenAI, Anthropic, etc.)
|
|
19
|
+
- **Streams**: Real-time streaming responses
|
|
20
|
+
- **Chat**: Conversational interfaces
|
|
21
|
+
- **Completion**: Text completion
|
|
22
|
+
- **Hooks**: React hooks for AI interactions
|
|
23
|
+
|
|
24
|
+
## Basic Setup
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// lib/ai.ts
|
|
28
|
+
import { createAI } from '@tanstack/ai'
|
|
29
|
+
|
|
30
|
+
export const ai = createAI({
|
|
31
|
+
provider: 'openai',
|
|
32
|
+
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
|
|
33
|
+
// Or use server-side proxy
|
|
34
|
+
baseUrl: '/api/ai',
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Chat Interface
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { useChat } from '@tanstack/ai-react'
|
|
42
|
+
import { ai } from '@/lib/ai'
|
|
43
|
+
|
|
44
|
+
function ChatInterface() {
|
|
45
|
+
const {
|
|
46
|
+
messages,
|
|
47
|
+
input,
|
|
48
|
+
setInput,
|
|
49
|
+
sendMessage,
|
|
50
|
+
isLoading,
|
|
51
|
+
error,
|
|
52
|
+
} = useChat({
|
|
53
|
+
ai,
|
|
54
|
+
model: 'gpt-4',
|
|
55
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
59
|
+
e.preventDefault()
|
|
60
|
+
if (input.trim()) {
|
|
61
|
+
sendMessage(input)
|
|
62
|
+
setInput('')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="chat-container">
|
|
68
|
+
<div className="messages">
|
|
69
|
+
{messages.map((message) => (
|
|
70
|
+
<div
|
|
71
|
+
key={message.id}
|
|
72
|
+
className={`message ${message.role}`}
|
|
73
|
+
>
|
|
74
|
+
{message.content}
|
|
75
|
+
</div>
|
|
76
|
+
))}
|
|
77
|
+
{isLoading && <div className="loading">Thinking...</div>}
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<form onSubmit={handleSubmit}>
|
|
81
|
+
<input
|
|
82
|
+
value={input}
|
|
83
|
+
onChange={(e) => setInput(e.target.value)}
|
|
84
|
+
placeholder="Type a message..."
|
|
85
|
+
disabled={isLoading}
|
|
86
|
+
/>
|
|
87
|
+
<button type="submit" disabled={isLoading || !input.trim()}>
|
|
88
|
+
Send
|
|
89
|
+
</button>
|
|
90
|
+
</form>
|
|
91
|
+
|
|
92
|
+
{error && <div className="error">{error.message}</div>}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Streaming Responses
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { useCompletion } from '@tanstack/ai-react'
|
|
102
|
+
import { ai } from '@/lib/ai'
|
|
103
|
+
|
|
104
|
+
function StreamingCompletion() {
|
|
105
|
+
const {
|
|
106
|
+
completion,
|
|
107
|
+
complete,
|
|
108
|
+
isLoading,
|
|
109
|
+
stop,
|
|
110
|
+
} = useCompletion({
|
|
111
|
+
ai,
|
|
112
|
+
model: 'gpt-4',
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const handleGenerate = () => {
|
|
116
|
+
complete('Write a short story about a robot learning to paint.')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div>
|
|
121
|
+
<button onClick={handleGenerate} disabled={isLoading}>
|
|
122
|
+
Generate Story
|
|
123
|
+
</button>
|
|
124
|
+
{isLoading && (
|
|
125
|
+
<button onClick={stop}>Stop</button>
|
|
126
|
+
)}
|
|
127
|
+
<div className="completion">
|
|
128
|
+
{completion}
|
|
129
|
+
{isLoading && <span className="cursor">|</span>}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Server-Side Proxy Pattern
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// For security, proxy AI calls through your server
|
|
140
|
+
|
|
141
|
+
// api/ai/chat.ts (server route)
|
|
142
|
+
import { OpenAI } from 'openai'
|
|
143
|
+
|
|
144
|
+
const openai = new OpenAI({
|
|
145
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
export async function POST(request: Request) {
|
|
149
|
+
const { messages, model } = await request.json()
|
|
150
|
+
|
|
151
|
+
const stream = await openai.chat.completions.create({
|
|
152
|
+
model,
|
|
153
|
+
messages,
|
|
154
|
+
stream: true,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return new Response(stream.toReadableStream(), {
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'text/event-stream',
|
|
160
|
+
'Cache-Control': 'no-cache',
|
|
161
|
+
'Connection': 'keep-alive',
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Client setup
|
|
167
|
+
export const ai = createAI({
|
|
168
|
+
provider: 'openai',
|
|
169
|
+
baseUrl: '/api/ai', // Use server proxy
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Chat with Context
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { useChat } from '@tanstack/ai-react'
|
|
177
|
+
|
|
178
|
+
function ContextualChat({ documentContent }: { documentContent: string }) {
|
|
179
|
+
const chat = useChat({
|
|
180
|
+
ai,
|
|
181
|
+
model: 'gpt-4',
|
|
182
|
+
systemPrompt: `You are analyzing the following document. Answer questions about it.
|
|
183
|
+
|
|
184
|
+
Document:
|
|
185
|
+
${documentContent}`,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
return <ChatUI {...chat} />
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Multi-Turn Conversation
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { useChat } from '@tanstack/ai-react'
|
|
196
|
+
import { useState } from 'react'
|
|
197
|
+
|
|
198
|
+
function ConversationWithHistory() {
|
|
199
|
+
const [conversations, setConversations] = useState<Conversation[]>([])
|
|
200
|
+
const [activeConversation, setActiveConversation] = useState<string | null>(null)
|
|
201
|
+
|
|
202
|
+
const chat = useChat({
|
|
203
|
+
ai,
|
|
204
|
+
model: 'gpt-4',
|
|
205
|
+
initialMessages: activeConversation
|
|
206
|
+
? conversations.find(c => c.id === activeConversation)?.messages
|
|
207
|
+
: [],
|
|
208
|
+
onFinish: (message) => {
|
|
209
|
+
// Save conversation
|
|
210
|
+
if (activeConversation) {
|
|
211
|
+
setConversations(prev =>
|
|
212
|
+
prev.map(c =>
|
|
213
|
+
c.id === activeConversation
|
|
214
|
+
? { ...c, messages: [...c.messages, message] }
|
|
215
|
+
: c
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const startNewConversation = () => {
|
|
223
|
+
const id = crypto.randomUUID()
|
|
224
|
+
setConversations(prev => [...prev, { id, messages: [] }])
|
|
225
|
+
setActiveConversation(id)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<div className="flex">
|
|
230
|
+
<aside>
|
|
231
|
+
<button onClick={startNewConversation}>New Chat</button>
|
|
232
|
+
{conversations.map(conv => (
|
|
233
|
+
<button
|
|
234
|
+
key={conv.id}
|
|
235
|
+
onClick={() => setActiveConversation(conv.id)}
|
|
236
|
+
className={activeConversation === conv.id ? 'active' : ''}
|
|
237
|
+
>
|
|
238
|
+
Conversation {conv.id.slice(0, 8)}
|
|
239
|
+
</button>
|
|
240
|
+
))}
|
|
241
|
+
</aside>
|
|
242
|
+
<main>
|
|
243
|
+
<ChatUI {...chat} />
|
|
244
|
+
</main>
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Function Calling
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { useChat } from '@tanstack/ai-react'
|
|
254
|
+
|
|
255
|
+
const functions = [
|
|
256
|
+
{
|
|
257
|
+
name: 'get_weather',
|
|
258
|
+
description: 'Get the current weather for a location',
|
|
259
|
+
parameters: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
properties: {
|
|
262
|
+
location: { type: 'string', description: 'City name' },
|
|
263
|
+
},
|
|
264
|
+
required: ['location'],
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
function ChatWithFunctions() {
|
|
270
|
+
const chat = useChat({
|
|
271
|
+
ai,
|
|
272
|
+
model: 'gpt-4',
|
|
273
|
+
functions,
|
|
274
|
+
onFunctionCall: async (name, args) => {
|
|
275
|
+
if (name === 'get_weather') {
|
|
276
|
+
const weather = await fetchWeather(args.location)
|
|
277
|
+
return JSON.stringify(weather)
|
|
278
|
+
}
|
|
279
|
+
return null
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return <ChatUI {...chat} />
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Integration with Query
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { useQuery } from '@tanstack/react-query'
|
|
291
|
+
import { useChat } from '@tanstack/ai-react'
|
|
292
|
+
|
|
293
|
+
function AIAssistedSearch({ query }: { query: string }) {
|
|
294
|
+
// Fetch data with Query
|
|
295
|
+
const { data: results } = useQuery({
|
|
296
|
+
queryKey: ['search', query],
|
|
297
|
+
queryFn: () => searchApi.search(query),
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// Use AI to summarize results
|
|
301
|
+
const { completion, complete } = useCompletion({
|
|
302
|
+
ai,
|
|
303
|
+
model: 'gpt-4',
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (results?.length) {
|
|
308
|
+
complete(`Summarize these search results:\n${JSON.stringify(results)}`)
|
|
309
|
+
}
|
|
310
|
+
}, [results])
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<div>
|
|
314
|
+
<h2>AI Summary</h2>
|
|
315
|
+
<p>{completion}</p>
|
|
316
|
+
<h2>Results</h2>
|
|
317
|
+
<ResultsList results={results} />
|
|
318
|
+
</div>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Conventions
|
|
324
|
+
|
|
325
|
+
1. **Server proxy** - Never expose API keys to client
|
|
326
|
+
2. **Streaming** - Use streaming for better UX
|
|
327
|
+
3. **Error handling** - Always handle AI errors gracefully
|
|
328
|
+
4. **Rate limiting** - Implement client-side throttling
|
|
329
|
+
5. **Token management** - Track and limit token usage
|
|
330
|
+
6. **Cancellation** - Allow users to stop generation
|
|
331
|
+
|
|
332
|
+
## Anti-Patterns
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// ❌ WRONG: API key in client
|
|
336
|
+
const ai = createAI({
|
|
337
|
+
provider: 'openai',
|
|
338
|
+
apiKey: 'sk-...', // Never do this!
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
// ✅ CORRECT: Use server proxy
|
|
342
|
+
const ai = createAI({
|
|
343
|
+
provider: 'openai',
|
|
344
|
+
baseUrl: '/api/ai',
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
// ❌ WRONG: No loading state
|
|
348
|
+
function Chat() {
|
|
349
|
+
const { messages, sendMessage } = useChat({ ai })
|
|
350
|
+
return <div>{messages.map(...)}</div>
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ✅ CORRECT: Show loading state
|
|
354
|
+
function Chat() {
|
|
355
|
+
const { messages, sendMessage, isLoading } = useChat({ ai })
|
|
356
|
+
return (
|
|
357
|
+
<div>
|
|
358
|
+
{messages.map(...)}
|
|
359
|
+
{isLoading && <Typing />}
|
|
360
|
+
</div>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ❌ WRONG: No error handling
|
|
365
|
+
const { completion } = useCompletion({ ai })
|
|
366
|
+
|
|
367
|
+
// ✅ CORRECT: Handle errors
|
|
368
|
+
const { completion, error } = useCompletion({ ai })
|
|
369
|
+
if (error) return <ErrorMessage error={error} />
|
|
370
|
+
```
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: TanStack DB Patterns (Beta)
|
|
3
|
+
description: >-
|
|
4
|
+
TanStack DB patterns for client-first reactive data stores. Activates when
|
|
5
|
+
implementing offline-first apps, local-first data, or reactive client databases.
|
|
6
|
+
NOTE: Beta library - API may change.
|
|
7
|
+
version: 1.0.0
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TanStack DB Patterns (Beta)
|
|
11
|
+
|
|
12
|
+
> **Beta Library**: TanStack DB is in beta. APIs may change between versions.
|
|
13
|
+
|
|
14
|
+
TanStack DB provides a client-first reactive data store with optional sync to remote sources.
|
|
15
|
+
|
|
16
|
+
## Core Concepts
|
|
17
|
+
|
|
18
|
+
- **Collections**: Named groups of documents (like tables)
|
|
19
|
+
- **Documents**: Individual records with unique IDs
|
|
20
|
+
- **Queries**: Reactive queries that update when data changes
|
|
21
|
+
- **Transactions**: Atomic operations across multiple documents
|
|
22
|
+
- **Sync**: Optional sync to remote backends
|
|
23
|
+
|
|
24
|
+
## Basic Setup
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// lib/db.ts
|
|
28
|
+
import { createDB, createCollection } from '@tanstack/db'
|
|
29
|
+
|
|
30
|
+
// Define document types
|
|
31
|
+
interface Post {
|
|
32
|
+
id: string
|
|
33
|
+
title: string
|
|
34
|
+
content: string
|
|
35
|
+
authorId: string
|
|
36
|
+
published: boolean
|
|
37
|
+
createdAt: number
|
|
38
|
+
updatedAt: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface User {
|
|
42
|
+
id: string
|
|
43
|
+
name: string
|
|
44
|
+
email: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create database
|
|
48
|
+
export const db = createDB({
|
|
49
|
+
collections: {
|
|
50
|
+
posts: createCollection<Post>(),
|
|
51
|
+
users: createCollection<User>(),
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## CRUD Operations
|
|
57
|
+
|
|
58
|
+
### Create
|
|
59
|
+
```typescript
|
|
60
|
+
import { db } from '@/lib/db'
|
|
61
|
+
|
|
62
|
+
// Insert a single document
|
|
63
|
+
const newPost = await db.posts.insert({
|
|
64
|
+
id: crypto.randomUUID(),
|
|
65
|
+
title: 'My Post',
|
|
66
|
+
content: 'Post content...',
|
|
67
|
+
authorId: 'user-1',
|
|
68
|
+
published: false,
|
|
69
|
+
createdAt: Date.now(),
|
|
70
|
+
updatedAt: Date.now(),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Insert multiple documents
|
|
74
|
+
await db.posts.insertMany([
|
|
75
|
+
{ id: '1', title: 'Post 1', ... },
|
|
76
|
+
{ id: '2', title: 'Post 2', ... },
|
|
77
|
+
])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Read
|
|
81
|
+
```typescript
|
|
82
|
+
// Get by ID
|
|
83
|
+
const post = await db.posts.get('post-id')
|
|
84
|
+
|
|
85
|
+
// Query with filters
|
|
86
|
+
const publishedPosts = await db.posts.findMany({
|
|
87
|
+
where: { published: true },
|
|
88
|
+
orderBy: { createdAt: 'desc' },
|
|
89
|
+
limit: 10,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Query with complex filters
|
|
93
|
+
const userPosts = await db.posts.findMany({
|
|
94
|
+
where: {
|
|
95
|
+
authorId: 'user-1',
|
|
96
|
+
published: true,
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Update
|
|
102
|
+
```typescript
|
|
103
|
+
// Update by ID
|
|
104
|
+
await db.posts.update('post-id', {
|
|
105
|
+
title: 'Updated Title',
|
|
106
|
+
updatedAt: Date.now(),
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Update with function
|
|
110
|
+
await db.posts.update('post-id', (post) => ({
|
|
111
|
+
...post,
|
|
112
|
+
viewCount: post.viewCount + 1,
|
|
113
|
+
updatedAt: Date.now(),
|
|
114
|
+
}))
|
|
115
|
+
|
|
116
|
+
// Update many
|
|
117
|
+
await db.posts.updateMany(
|
|
118
|
+
{ where: { authorId: 'user-1' } },
|
|
119
|
+
{ published: false }
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Delete
|
|
124
|
+
```typescript
|
|
125
|
+
// Delete by ID
|
|
126
|
+
await db.posts.delete('post-id')
|
|
127
|
+
|
|
128
|
+
// Delete many
|
|
129
|
+
await db.posts.deleteMany({
|
|
130
|
+
where: { published: false },
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Reactive Queries in React
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { useQuery } from '@tanstack/db-react'
|
|
138
|
+
import { db } from '@/lib/db'
|
|
139
|
+
|
|
140
|
+
function PostList() {
|
|
141
|
+
// Reactive query - updates when data changes
|
|
142
|
+
const posts = useQuery(
|
|
143
|
+
db.posts.query({
|
|
144
|
+
where: { published: true },
|
|
145
|
+
orderBy: { createdAt: 'desc' },
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<ul>
|
|
151
|
+
{posts.map((post) => (
|
|
152
|
+
<PostCard key={post.id} post={post} />
|
|
153
|
+
))}
|
|
154
|
+
</ul>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function PostDetail({ postId }: { postId: string }) {
|
|
159
|
+
// Single document query
|
|
160
|
+
const post = useQuery(db.posts.get(postId))
|
|
161
|
+
|
|
162
|
+
if (!post) return <NotFound />
|
|
163
|
+
|
|
164
|
+
return <article>{post.title}</article>
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Transactions
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { db } from '@/lib/db'
|
|
172
|
+
|
|
173
|
+
async function transferPost(postId: string, newAuthorId: string) {
|
|
174
|
+
await db.transaction(async (tx) => {
|
|
175
|
+
// Get current post
|
|
176
|
+
const post = await tx.posts.get(postId)
|
|
177
|
+
if (!post) throw new Error('Post not found')
|
|
178
|
+
|
|
179
|
+
// Update post author
|
|
180
|
+
await tx.posts.update(postId, {
|
|
181
|
+
authorId: newAuthorId,
|
|
182
|
+
updatedAt: Date.now(),
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Update user post counts (if tracking)
|
|
186
|
+
await tx.users.update(post.authorId, (user) => ({
|
|
187
|
+
...user,
|
|
188
|
+
postCount: user.postCount - 1,
|
|
189
|
+
}))
|
|
190
|
+
|
|
191
|
+
await tx.users.update(newAuthorId, (user) => ({
|
|
192
|
+
...user,
|
|
193
|
+
postCount: user.postCount + 1,
|
|
194
|
+
}))
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Sync with Remote Backend
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// lib/db.ts
|
|
203
|
+
import { createDB, createCollection, createSyncProvider } from '@tanstack/db'
|
|
204
|
+
|
|
205
|
+
const syncProvider = createSyncProvider({
|
|
206
|
+
// Pull changes from server
|
|
207
|
+
pull: async (collection, lastSync) => {
|
|
208
|
+
const response = await fetch(`/api/${collection}/sync?since=${lastSync}`)
|
|
209
|
+
return response.json()
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Push changes to server
|
|
213
|
+
push: async (collection, changes) => {
|
|
214
|
+
await fetch(`/api/${collection}/sync`, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
body: JSON.stringify(changes),
|
|
217
|
+
})
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
export const db = createDB({
|
|
222
|
+
collections: {
|
|
223
|
+
posts: createCollection<Post>(),
|
|
224
|
+
users: createCollection<User>(),
|
|
225
|
+
},
|
|
226
|
+
sync: syncProvider,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// Trigger sync
|
|
230
|
+
await db.sync()
|
|
231
|
+
|
|
232
|
+
// Auto-sync on interval
|
|
233
|
+
setInterval(() => db.sync(), 30000)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Offline-First Pattern
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { useQuery, useMutation } from '@tanstack/db-react'
|
|
240
|
+
import { db } from '@/lib/db'
|
|
241
|
+
|
|
242
|
+
function CreatePostForm() {
|
|
243
|
+
const createPost = useMutation(db.posts.insert)
|
|
244
|
+
|
|
245
|
+
const handleSubmit = async (data: PostInput) => {
|
|
246
|
+
// Immediately available locally
|
|
247
|
+
await createPost.mutate({
|
|
248
|
+
id: crypto.randomUUID(),
|
|
249
|
+
...data,
|
|
250
|
+
createdAt: Date.now(),
|
|
251
|
+
updatedAt: Date.now(),
|
|
252
|
+
_pending: true, // Mark as pending sync
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Sync will happen in background when online
|
|
256
|
+
if (navigator.onLine) {
|
|
257
|
+
db.sync()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return <form onSubmit={handleSubmit}>{/* form fields */}</form>
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Show pending items differently
|
|
265
|
+
function PostCard({ post }: { post: Post }) {
|
|
266
|
+
return (
|
|
267
|
+
<div className={post._pending ? 'opacity-50' : ''}>
|
|
268
|
+
{post.title}
|
|
269
|
+
{post._pending && <span>Syncing...</span>}
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Integration with TanStack Query
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Hybrid approach: DB for offline, Query for server sync
|
|
279
|
+
import { useQuery as useReactQuery } from '@tanstack/react-query'
|
|
280
|
+
import { useQuery as useDBQuery } from '@tanstack/db-react'
|
|
281
|
+
import { db } from '@/lib/db'
|
|
282
|
+
|
|
283
|
+
function PostList() {
|
|
284
|
+
// Local DB for immediate data
|
|
285
|
+
const localPosts = useDBQuery(
|
|
286
|
+
db.posts.query({ where: { published: true } })
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
// Server query for sync
|
|
290
|
+
const { data: serverPosts } = useReactQuery({
|
|
291
|
+
queryKey: ['posts', 'published'],
|
|
292
|
+
queryFn: () => postApi.getPosts({ published: true }),
|
|
293
|
+
onSuccess: (posts) => {
|
|
294
|
+
// Update local DB with server data
|
|
295
|
+
db.posts.upsertMany(posts)
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// Use local data (always available, even offline)
|
|
300
|
+
return (
|
|
301
|
+
<ul>
|
|
302
|
+
{localPosts.map((post) => (
|
|
303
|
+
<PostCard key={post.id} post={post} />
|
|
304
|
+
))}
|
|
305
|
+
</ul>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## When to Use TanStack DB
|
|
311
|
+
|
|
312
|
+
| Scenario | Solution |
|
|
313
|
+
|----------|----------|
|
|
314
|
+
| Standard server data | TanStack Query |
|
|
315
|
+
| Offline-first app | TanStack DB |
|
|
316
|
+
| Local-first with sync | TanStack DB + sync |
|
|
317
|
+
| Real-time collaboration | TanStack DB + WebSocket sync |
|
|
318
|
+
| Complex client state | TanStack DB or Store |
|
|
319
|
+
|
|
320
|
+
## Conventions
|
|
321
|
+
|
|
322
|
+
1. **Type your collections** - Always define document interfaces
|
|
323
|
+
2. **Use transactions** - For multi-document operations
|
|
324
|
+
3. **Handle offline** - Design for offline-first
|
|
325
|
+
4. **Sync strategy** - Define clear sync patterns
|
|
326
|
+
5. **Combine with Query** - Use Query for pure server data
|
|
327
|
+
|
|
328
|
+
## Anti-Patterns
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// ❌ WRONG: Not using transactions for related updates
|
|
332
|
+
await db.posts.delete(postId)
|
|
333
|
+
await db.comments.deleteMany({ where: { postId } }) // Could fail leaving orphans
|
|
334
|
+
|
|
335
|
+
// ✅ CORRECT: Use transaction
|
|
336
|
+
await db.transaction(async (tx) => {
|
|
337
|
+
await tx.posts.delete(postId)
|
|
338
|
+
await tx.comments.deleteMany({ where: { postId } })
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
// ❌ WRONG: Using DB for pure server data without offline need
|
|
342
|
+
const posts = useDBQuery(db.posts.query({}))
|
|
343
|
+
|
|
344
|
+
// ✅ CORRECT: Use Query for server data
|
|
345
|
+
const { data: posts } = useQuery(postsQueryOptions())
|
|
346
|
+
```
|