ChatStore
The user-bound persistence contract, its ownership invariants, pagination options, and usage record seam.
ChatStore is the persistence contract used by createChatHandler. A factory is
called after authentication and returns an instance bound to one verified user.
import type { ChatStore, ChatStoreFactory } from '@mordn/chat-widget/server';
type ChatStoreFactory = (userId: string) => ChatStore;
Interface
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
getConversationreturnsnullfor both missing and foreign conversations.ensureConversationandsaveTurnthrowConversationOwnershipErrorfor a foreign conversation id.listMessagesnever returns another user’s messages.saveTurnis idempotent on message id and persists the completepartsarray.- Implementations should clamp requested page sizes. The Drizzle store caps
message pages at 100 and supports
beforepagination. deleteConversationremoves only the bound user’s row. Attachment blob purge is coordinated by the handler through the storage adapter.
Core types
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.