Skip to content

ChatStore

ChatStore is the persistence contract used by createChatHandler. A factory is called after authentication and returns an instance bound to one verified user.

code
import type { ChatStore, ChatStoreFactory } from '@mordn/chat-widget/server';
 
type ChatStoreFactory = (userId: string) => ChatStore;

Interface

code
interface ChatStore {
  readonly userId: string;
 
  listConversations(): Promise<StoredConversation[]>;
  getConversation(id: string): Promise<StoredConversation | null>;
  ensureConversation(id: string, init?: { title?: string }): Promise<StoredConversation>;
  renameConversation(id: string, title: string): Promise<void>;
  deleteConversation(id: string): Promise<boolean>;
 
  listMessages(
    conversationId: string,
    options?: { limit?: number; before?: Date },
  ): Promise<StoredMessage[]>;
 
  saveTurn(input: SaveTurnInput): Promise<void>;
}

Required invariants

  • getConversation returns null for both missing and foreign conversations.
  • ensureConversation and saveTurn throw ConversationOwnershipError for a foreign conversation id.
  • listMessages never returns another user's messages.
  • saveTurn is idempotent on message id and persists the complete parts array.
  • Implementations should clamp requested page sizes. The Drizzle store caps message pages at 100 and supports before pagination.
  • deleteConversation removes only the bound user's row. Attachment blob purge is coordinated by the handler through the storage adapter.

Core types

code
interface StoredConversation {
  id: string;
  title: string;
  metadata: Record<string, unknown> | null;
  createdAt: Date;
  updatedAt: Date;
  messageCount?: number;
}
 
interface StoredMessage {
  id: string;
  role: 'user' | 'assistant' | 'system';
  parts: UIMessage['parts'];
  text: string;
  model?: string;
  createdAt: Date;
}
 
interface SaveTurnInput {
  conversationId: string;
  messages: UIMessage[];
  model?: string;
  usage?: UsageRecord;
}

Stores may persist usage; an implementation that does not support usage may ignore it. Usage accounting must never compromise message ownership or persistence.

Custom implementations

Treat the method contracts as security requirements. See Custom store & adapter for an implementation skeleton and Security model for the threat model.