Getting Started
What is store-ai?
store-ai is a framework-agnostic, store-agnostic AI state management library for TypeScript. It sits between your AI API and your UI framework, handling both streaming and non-streaming responses:
Stream or Response -> Pipeline (middleware) -> Core Store -> Store Adapter -> Framework Adapter -> UIYou provide the stream (or a complete response). store-ai manages the state. You choose your store and framework.
Installation
Install the core package:
npm install @store-ai/coreThen add adapters for your stack:
# Store adapters (pick one or none)
npm install @store-ai/zustand # if you use Zustand
npm install @store-ai/jotai # if you use Jotai
npm install @store-ai/nanostores # if you use Nanostores
npm install @store-ai/valtio # if you use Valtio
npm install @store-ai/redux # if you use Redux Toolkit
npm install @store-ai/tanstack # if you use @tanstack/store
# Framework adapters (pick one or none)
npm install @store-ai/react # React hooks
npm install @store-ai/vue # Vue composables
npm install @store-ai/svelte # Svelte stores
npm install @store-ai/solid # Solid signals
npm install @store-ai/preact # Preact hooks
npm install @store-ai/angular # Angular signals
npm install @store-ai/lit # Lit ReactiveControllerYou don't need both a store adapter and a framework adapter. Use whichever fits your architecture. The core package works on its own with vanilla subscribe() calls.
Quick Start: Vanilla
Zero dependencies, no framework, no store library:
import { createAIStore, anthropic } from '@store-ai/core';
const store = createAIStore({
provider: anthropic(),
});
// Start streaming
store.submit({
message: 'Explain quantum computing',
stream: await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt: 'Explain quantum computing' }),
}).then((r) => r.body!),
});
// Subscribe to changes
store.subscribe('text', (text) => {
document.getElementById('output')!.textContent = text;
});
store.subscribe('status', (status) => {
console.log('Status:', status); // idle -> connecting -> streaming -> complete
});Quick Start: React + Zustand
import { createAIStore, anthropic } from '@store-ai/core';
import { toZustand } from '@store-ai/zustand';
import { useStore } from 'zustand';
// Create once, outside components
const aiStore = createAIStore({ provider: anthropic() });
const { store: zStore } = toZustand(aiStore);
function ChatMessage() {
const text = useStore(zStore, (s) => s.text);
const status = useStore(zStore, (s) => s.status);
return <div>{status === 'streaming' ? text + '...' : text}</div>;
}
function ChatInput() {
const [input, setInput] = useState('');
const handleSubmit = async () => {
const message = input;
setInput('');
aiStore.submit({
message,
stream: await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message }),
}).then((r) => r.body!),
});
};
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={handleSubmit}>Send</button>
<button onClick={() => aiStore.abort()}>Stop</button>
</div>
);
}Quick Start: React Hooks
If you don't need Zustand, use the built-in React hooks:
import { createAIStore, anthropic } from '@store-ai/core';
import { useAIText, useAIStatus } from '@store-ai/react';
const store = createAIStore({ provider: anthropic() });
function StreamingText() {
const text = useAIText(store);
const status = useAIStatus(store);
return <p>{status === 'streaming' ? text + '...' : text}</p>;
}Quick Start: Vue
import { createAIStore, openai } from '@store-ai/core';
import { useAI } from '@store-ai/vue';
const store = createAIStore({ provider: openai() });
// In setup()
const { text, status, messages } = useAI(store);Quick Start: Svelte
<script>
import { createAIStore, openai } from '@store-ai/core';
import { createAIReadable } from '@store-ai/svelte';
const store = createAIStore({ provider: openai() });
const ai = createAIReadable(store);
</script>
<p>{$ai.text}</p>
<span>Status: {$ai.status}</span>Quick Start: Solid
import { createAIStore, anthropic } from '@store-ai/core';
import { useAI } from '@store-ai/solid';
const store = createAIStore({ provider: anthropic() });
function App() {
const state = useAI(store);
return <p>{state().text}</p>;
}Quick Start: Non-Streaming (Request-Response)
If your backend returns a complete response instead of streaming, use response:
import { createAIStore } from '@store-ai/core';
const store = createAIStore();
// Call your API
const result = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: 'Show me cats' }),
}).then((r) => r.json());
// Feed the complete response — no streaming needed
store.submit({
message: 'Show me cats',
response: {
text: result.text,
object: result.structuredData,
usage: { inputTokens: result.usage.input, outputTokens: result.usage.output },
},
});
// Subscribe as normal — status goes idle → streaming → complete instantly
store.subscribe('text', (text) => {
document.getElementById('output')!.textContent = text;
});All middleware (persistence, logging, cost tracking, etc.) works identically with non-streaming responses.
Next Steps
- Core Concepts -- understand the state shape, provider adapters, and middleware pipeline
- Providers -- configure Anthropic, OpenAI, or custom stream sources
- Middleware -- add logging, validation, persistence, and more
- Store Adapters -- connect to Zustand, Jotai, Nanostores, Valtio, Redux, or TanStack
- Migration -- coming from Vercel AI SDK or
@ai-sdk-tools/store