@aichatkit/storage-adapter
Version:
Base storage adapter for Hypermode ChatKit
387 lines (272 loc) • 10.4 kB
Markdown
# /storage-adapter
Base storage adapter abstract class for Hypermode ChatKit with backend synchronization support.
## Installation
```bash
npm install /storage-adapter
```
## Usage
This package provides the base abstract class for implementing storage adapters. You typically won't
use this package directly, but rather create or use an implementation like
`/localstorage-adapter`.
```typescript
import { StorageAdapter } from "@aichatkit/storage-adapter"
import { Conversation, Message } from "@aichatkit/types"
// Example: Create a custom adapter implementation
class CustomStorageAdapter extends StorageAdapter {
private conversations: Map<string, Conversation> = new Map()
private agentMapping: Map<string, string> = new Map()
async saveConversation(conversation: Conversation): Promise<void> {
this.conversations.set(conversation.id, { ...conversation })
}
async getConversation(id: string): Promise<Conversation | null> {
return this.conversations.get(id) || null
}
async getAllConversations(): Promise<Conversation[]> {
return Array.from(this.conversations.values())
}
async deleteConversation(id: string): Promise<boolean> {
const deleted = this.conversations.delete(id)
this.agentMapping.delete(id)
return deleted
}
async addMessage(conversationId: string, message: Message): Promise<Conversation | null> {
const conversation = this.conversations.get(conversationId)
if (!conversation) return null
conversation.messages.push(message)
await this.saveConversation(conversation)
return conversation
}
async getConversationHistory(conversationId: string): Promise<Message[]> {
// Check if we have network callbacks for backend sync
const agentId = await this.getConversationAgent(conversationId)
if (agentId && this.callbacks?.getConversationHistory) {
try {
const backendMessages = await this.callbacks.getConversationHistory(agentId)
// Update local storage with backend data
const conversation = await this.getConversation(conversationId)
if (conversation) {
conversation.messages = backendMessages
await this.saveConversation(conversation)
}
return backendMessages
} catch (error) {
console.error("Backend sync failed, using local data:", error)
}
}
// Fall back to local data
const conversation = await this.getConversation(conversationId)
return conversation?.messages || []
}
async clearConversationHistory(conversationId: string): Promise<void> {
const agentId = await this.getConversationAgent(conversationId)
// Clear on backend if possible
if (agentId && this.callbacks?.clearConversationHistory) {
try {
await this.callbacks.clearConversationHistory(agentId)
} catch (error) {
console.error("Failed to clear backend history:", error)
}
}
// Clear locally
const conversation = await this.getConversation(conversationId)
if (conversation) {
conversation.messages = []
await this.saveConversation(conversation)
}
}
async setConversationAgent(conversationId: string, agentId: string): Promise<void> {
this.agentMapping.set(conversationId, agentId)
}
async getConversationAgent(conversationId: string): Promise<string | null> {
return this.agentMapping.get(conversationId) || null
}
}
```
## Abstract Methods
All storage adapters must implement these core methods:
### saveConversation(conversation: Conversation): Promise<void>
Saves a conversation to storage.
```typescript
abstract saveConversation(conversation: Conversation): Promise<void>;
```
**Parameters**:
- `conversation`: The conversation object to save
**Example**:
```typescript
await adapter.saveConversation({
id: "conv-1",
title: "My Chat",
messages: [],
})
```
### getConversation(id: string): Promise<Conversation | null>
Retrieves a conversation from storage by ID.
```typescript
abstract getConversation(id: string): Promise<Conversation | null>;
```
**Parameters**:
- `id`: ID of the conversation to retrieve
**Returns**: Promise resolving to the conversation or null if not found
### getAllConversations(): Promise<Conversation[]>
Retrieves all conversations from storage.
```typescript
abstract getAllConversations(): Promise<Conversation[]>;
```
**Returns**: Promise resolving to an array of all conversations
### deleteConversation(id: string): Promise<boolean>
Deletes a conversation from storage.
```typescript
abstract deleteConversation(id: string): Promise<boolean>;
```
**Parameters**:
- `id`: ID of the conversation to delete
**Returns**: Promise resolving to true if successful, false otherwise
### addMessage(conversationId: string, message: Message): Promise<Conversation | null>
Adds a message to a conversation.
```typescript
abstract addMessage(conversationId: string, message: Message): Promise<Conversation | null>;
```
**Parameters**:
- `conversationId`: ID of the conversation
- `message`: Message to add
**Returns**: Promise resolving to the updated conversation or null if not found
## Backend Synchronization Methods
These methods enable synchronization with backend agents:
### getConversationHistory(conversationId: string): Promise<Message[]>
Gets conversation history with backend synchronization.
```typescript
abstract getConversationHistory(conversationId: string): Promise<Message[]>;
```
This method should:
1. Check if a backend agent exists for the conversation
2. Sync with backend if available
3. Fall back to local storage if backend sync fails
### clearConversationHistory(conversationId: string): Promise<void>
Clears conversation history both locally and on backend.
```typescript
abstract clearConversationHistory(conversationId: string): Promise<void>;
```
### setConversationAgent(conversationId: string, agentId: string): Promise<void>
Maps a conversation to a backend agent ID.
```typescript
abstract setConversationAgent(conversationId: string, agentId: string): Promise<void>;
```
### getConversationAgent(conversationId: string): Promise<string | null>
Gets the backend agent ID for a conversation.
```typescript
abstract getConversationAgent(conversationId: string): Promise<string | null>;
```
## Optional Methods
### initialize(config?: Record<string, any>): Promise<void>
Optional initialization method.
```typescript
async initialize(config?: Record<string, any>): Promise<void> {
return Promise.resolve();
}
```
### syncAllConversationsWithBackend(): Promise<void>
Syncs all conversations with backend (called on app load).
```typescript
async syncAllConversationsWithBackend?(): Promise<void> {
return Promise.resolve();
}
```
## Network Callbacks
Storage adapters can receive network callbacks for backend synchronization:
### StorageAdapterCallbacks Interface
```typescript
interface StorageAdapterCallbacks {
getConversationHistory?: (agentId: string) => Promise<Message[]>
clearConversationHistory?: (agentId: string) => Promise<void>
}
```
### setNetworkCallbacks(callbacks: StorageAdapterCallbacks): void
Sets network callbacks for backend communication.
```typescript
import { NetworkAdapter } from "@aichatkit/network-adapter"
const networkAdapter = new SomeNetworkAdapter()
const storageAdapter = new SomeStorageAdapter()
// Connect storage with network for syncing
storageAdapter.setNetworkCallbacks({
getConversationHistory: (agentId) => networkAdapter.getConversationHistory(agentId),
clearConversationHistory: (agentId) => networkAdapter.clearConversationHistory(agentId),
})
```
## Data Types
### Conversation
```typescript
interface Conversation {
id: string
title: string
messages: Message[]
}
```
### Message
```typescript
interface Message {
id: string | number
content: string
role: "user" | "assistant"
timestamp?: string
}
```
## Agent-Conversation Mapping
Storage adapters maintain a mapping between conversations and backend agents:
```typescript
// When creating a new conversation with an agent
await storageAdapter.setConversationAgent("conv-1", "agent-123")
// When retrieving the agent for a conversation
const agentId = await storageAdapter.getConversationAgent("conv-1")
// When deleting a conversation, also remove the agent mapping
await storageAdapter.deleteConversation("conv-1") // Should also remove agent mapping
```
## Synchronization Flow
1. **App Initialization**: Call `syncAllConversationsWithBackend()` to get latest state
2. **Conversation Switch**: Sync specific conversation when user switches to it
3. **Message History**: Always try backend first, fall back to local storage
4. **Background Sync**: Periodically sync to keep data fresh
### Example Sync Implementation
```typescript
async getConversationHistory(conversationId: string): Promise<Message[]> {
const agentId = await this.getConversationAgent(conversationId);
if (agentId && this.callbacks?.getConversationHistory) {
try {
// Try to get latest from backend
const backendMessages = await this.callbacks.getConversationHistory(agentId);
// Update local storage with backend data
const conversation = await this.getConversation(conversationId);
if (conversation) {
conversation.messages = backendMessages;
await this.saveConversation(conversation);
}
return backendMessages;
} catch (error) {
console.error('Backend sync failed:', error);
// Continue to local fallback
}
}
// Fall back to local storage
const conversation = await this.getConversation(conversationId);
return conversation?.messages || [];
}
```
## Available Implementations
- **[/localstorage-adapter](../localstorage-adapter)**: Browser localStorage
implementation
- **Custom implementations**: Create your own for databases, cloud storage, etc.
## Testing
Mock storage adapters for testing:
```typescript
class MockStorageAdapter extends StorageAdapter {
private data: Map<string, Conversation> = new Map()
async saveConversation(conversation: Conversation): Promise<void> {
this.data.set(conversation.id, { ...conversation })
}
async getConversation(id: string): Promise<Conversation | null> {
return this.data.get(id) || null
}
// ... implement other methods
}
```
## License
[MIT](../../LICENSE) © Hypermode