Storage API

IStorage abstracts persistence for conversations and messages.

Interface

export interface IStorage {
  saveMessage(message: Message): Promise<void>;
  getMessage(id: string): Promise<Message | undefined>;
  getMessages(query: MessageQuery): Promise<CursorPage<Message>>;
  updateMessage(id: string, update: Partial<Message>): Promise<void>;
  deleteMessage(id: string): Promise<void>;
 
  saveConversation(conversation: Conversation): Promise<void>;
  getConversation(id: string): Promise<Conversation | undefined>;
  getConversations(query?: ConversationQuery): Promise<CursorPage<Conversation>>;
  updateConversation(id: string, update: Partial<Conversation>): Promise<void>;
  deleteConversation(id: string): Promise<void>;
 
  clear(): Promise<void>;
}

LocalStorage Adapter (Browser)

import type {
  Conversation,
  ConversationQuery,
  CursorPage,
  IStorage,
  Message,
  MessageQuery,
} from '@kaira/chat-core';
 
const MESSAGE_KEY = 'kaira:messages';
const CONVERSATION_KEY = 'kaira:conversations';
 
export class LocalStorageAdapter implements IStorage {
  async saveMessage(message: Message): Promise<void> {
    const messages = this.readMessages();
    messages[message.id] = message;
    this.write(MESSAGE_KEY, messages);
  }
 
  async getMessage(id: string): Promise<Message | undefined> {
    return this.readMessages()[id];
  }
 
  async getMessages(query: MessageQuery): Promise<CursorPage<Message>> {
    const items = Object.values(this.readMessages()).filter(
      (message) => message.conversationId === query.conversationId,
    );
    return { items, hasMore: false };
  }
 
  async updateMessage(id: string, update: Partial<Message>): Promise<void> {
    const messages = this.readMessages();
    const current = messages[id];
    if (!current) return;
    messages[id] = { ...current, ...update };
    this.write(MESSAGE_KEY, messages);
  }
 
  async deleteMessage(id: string): Promise<void> {
    const messages = this.readMessages();
    delete messages[id];
    this.write(MESSAGE_KEY, messages);
  }
 
  async saveConversation(conversation: Conversation): Promise<void> {
    const conversations = this.readConversations();
    conversations[conversation.id] = conversation;
    this.write(CONVERSATION_KEY, conversations);
  }
 
  async getConversation(id: string): Promise<Conversation | undefined> {
    return this.readConversations()[id];
  }
 
  async getConversations(_query?: ConversationQuery): Promise<CursorPage<Conversation>> {
    return { items: Object.values(this.readConversations()), hasMore: false };
  }
 
  async updateConversation(id: string, update: Partial<Conversation>): Promise<void> {
    const conversations = this.readConversations();
    const current = conversations[id];
    if (!current) return;
    conversations[id] = { ...current, ...update };
    this.write(CONVERSATION_KEY, conversations);
  }
 
  async deleteConversation(id: string): Promise<void> {
    const conversations = this.readConversations();
    delete conversations[id];
    this.write(CONVERSATION_KEY, conversations);
  }
 
  async clear(): Promise<void> {
    localStorage.removeItem(MESSAGE_KEY);
    localStorage.removeItem(CONVERSATION_KEY);
  }
 
  private readMessages(): Record<string, Message> {
    return this.read<Record<string, Message>>(MESSAGE_KEY);
  }
 
  private readConversations(): Record<string, Conversation> {
    return this.read<Record<string, Conversation>>(CONVERSATION_KEY);
  }
 
  private read<T>(key: string): T {
    const raw = localStorage.getItem(key);
    if (!raw) return {} as T;
    return JSON.parse(raw) as T;
  }
 
  private write<T>(key: string, value: T): void {
    localStorage.setItem(key, JSON.stringify(value));
  }
}

Database Adapter Pattern

// Implement IStorage by translating methods to your backend API or ORM.
// Keep conversion logic isolated in this adapter layer.