UNPKG

@aichatkit/storage-adapter

Version:
387 lines (272 loc) 10.4 kB
# @aichatkit/storage-adapter Base storage adapter abstract class for Hypermode ChatKit with backend synchronization support. ## Installation ```bash npm install @aichatkit/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 `@aichatkit/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 - **[@aichatkit/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