The chatbot uses PostgreSQL with Drizzle ORM. All schemas are defined in lib/db/schema.ts.
Tables
User
Stores user accounts and authentication data.
Primary key, auto-generated UUID
User email address, unique identifier for login
Hashed password (nullable for OAuth-only users)
export const user = pgTable("User", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
email: varchar("email", { length: 64 }).notNull(),
password: varchar("password", { length: 64 }),
});
From lib/db/schema.ts:14-18
Chat
Stores chat conversations.
Primary key, auto-generated UUID
When the chat was created
Chat title (auto-generated or user-defined)
Foreign key to User table
visibility
enum
default:"private"
required
Chat visibility: "public" or "private"
export const chat = pgTable("Chat", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
createdAt: timestamp("createdAt").notNull(),
title: text("title").notNull(),
userId: uuid("userId")
.notNull()
.references(() => user.id),
visibility: varchar("visibility", { enum: ["public", "private"] })
.notNull()
.default("private"),
});
From lib/db/schema.ts:22-32
Message_v2
Stores chat messages with support for multimodal content (text and files).
This is the current message schema. The old Message table is deprecated.
Primary key, auto-generated UUID
Foreign key to Chat table
Message role: "user", "assistant", or "system"
Array of message parts (text or file objects)
Array of file attachments
When the message was created
export const message = pgTable("Message_v2", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
chatId: uuid("chatId")
.notNull()
.references(() => chat.id),
role: varchar("role").notNull(),
parts: json("parts").notNull(),
attachments: json("attachments").notNull(),
createdAt: timestamp("createdAt").notNull(),
});
From lib/db/schema.ts:50-59
Parts structure
The parts field contains an array of content blocks:
{
"type": "text",
"text": "What is the weather like today?"
}
Vote_v2
Stores user votes (upvotes/downvotes) on messages.
Part of composite primary key, foreign key to Chat
Part of composite primary key, foreign key to Message_v2
true for upvote, false for downvote
export const vote = pgTable(
"Vote_v2",
{
chatId: uuid("chatId")
.notNull()
.references(() => chat.id),
messageId: uuid("messageId")
.notNull()
.references(() => message.id),
isUpvoted: boolean("isUpvoted").notNull(),
},
(table) => {
return {
pk: primaryKey({ columns: [table.chatId, table.messageId] }),
};
}
);
From lib/db/schema.ts:85-101
Composite Primary Key: (chatId, messageId) ensures one vote per message.
Document
Stores AI-generated documents and artifacts with versioning.
Part of composite primary key, auto-generated UUID
Part of composite primary key, enables versioning
Document content (nullable)
kind
enum
default:"text"
required
Document type: "text", "code", "image", or "sheet"
Foreign key to User table
export const document = pgTable(
"Document",
{
id: uuid("id").notNull().defaultRandom(),
createdAt: timestamp("createdAt").notNull(),
title: text("title").notNull(),
content: text("content"),
kind: varchar("text", { enum: ["text", "code", "image", "sheet"] })
.notNull()
.default("text"),
userId: uuid("userId")
.notNull()
.references(() => user.id),
},
(table) => {
return {
pk: primaryKey({ columns: [table.id, table.createdAt] }),
};
}
);
From lib/db/schema.ts:105-124
Composite Primary Key: (id, createdAt) allows multiple versions of the same document.
Suggestion
Stores AI-generated editing suggestions for documents.
Primary key, auto-generated UUID
Part of composite foreign key to Document
Part of composite foreign key to Document, links to specific version
Suggested replacement text
Optional description of the suggestion
isResolved
boolean
default:"false"
required
Whether suggestion has been accepted/rejected
Foreign key to User table
When suggestion was created
export const suggestion = pgTable(
"Suggestion",
{
id: uuid("id").notNull().defaultRandom(),
documentId: uuid("documentId").notNull(),
documentCreatedAt: timestamp("documentCreatedAt").notNull(),
originalText: text("originalText").notNull(),
suggestedText: text("suggestedText").notNull(),
description: text("description"),
isResolved: boolean("isResolved").notNull().default(false),
userId: uuid("userId")
.notNull()
.references(() => user.id),
createdAt: timestamp("createdAt").notNull(),
},
(table) => ({
pk: primaryKey({ columns: [table.id] }),
documentRef: foreignKey({
columns: [table.documentId, table.documentCreatedAt],
foreignColumns: [document.id, document.createdAt],
}),
})
);
From lib/db/schema.ts:128-150
Composite Foreign Key: (documentId, documentCreatedAt) references specific document version.
Stream
Stores resumable stream IDs for chat responses.
Primary key, stream identifier
Foreign key to Chat table
export const stream = pgTable(
"Stream",
{
id: uuid("id").notNull().defaultRandom(),
chatId: uuid("chatId").notNull(),
createdAt: timestamp("createdAt").notNull(),
},
(table) => ({
pk: primaryKey({ columns: [table.id] }),
chatRef: foreignKey({
columns: [table.chatId],
foreignColumns: [chat.id],
}),
})
);
From lib/db/schema.ts:154-168
Stream records enable resumable streaming when Redis is configured, allowing interrupted streams to be resumed.
Relationships
User relationships
- One-to-many with Chat: Users can create multiple chats
- One-to-many with Document: Users can create multiple documents
- One-to-many with Suggestion: Suggestions belong to document owners
Chat relationships
- One-to-many with Message: Chats contain multiple messages
- One-to-many with Vote: Chats can have votes on their messages
- One-to-many with Stream: Chats can have multiple stream sessions
Document relationships
- One-to-many with Suggestion: Documents can have multiple suggestions
- Versioning: Same
id with different createdAt creates versions
Cascade deletes
When you delete a chat:
- All messages in the chat are deleted
- All votes on those messages are deleted
- All stream records are deleted
When you delete a document version:
- All suggestions for that version are deleted
Deprecated tables
Message (deprecated)
Old message table without multimodal support.
export const messageDeprecated = pgTable("Message", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
chatId: uuid("chatId")
.notNull()
.references(() => chat.id),
role: varchar("role").notNull(),
content: json("content").notNull(),
createdAt: timestamp("createdAt").notNull(),
});
From lib/db/schema.ts:38-46
Vote (deprecated)
Old vote table referencing deprecated Message table.
export const voteDeprecated = pgTable(
"Vote",
{
chatId: uuid("chatId")
.notNull()
.references(() => chat.id),
messageId: uuid("messageId")
.notNull()
.references(() => messageDeprecated.id),
isUpvoted: boolean("isUpvoted").notNull(),
},
(table) => {
return {
pk: primaryKey({ columns: [table.chatId, table.messageId] }),
};
}
);
From lib/db/schema.ts:65-81
TypeScript types
All tables export TypeScript types for type-safe queries:
export type User = InferSelectModel<typeof user>;
export type Chat = InferSelectModel<typeof chat>;
export type DBMessage = InferSelectModel<typeof message>;
export type Vote = InferSelectModel<typeof vote>;
export type Document = InferSelectModel<typeof document>;
export type Suggestion = InferSelectModel<typeof suggestion>;
export type Stream = InferSelectModel<typeof stream>;
These types are inferred from the schema definitions using Drizzle’s InferSelectModel utility.