12 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 21-chat-foundation | 01 | execute | 1 |
|
|
true |
|
|
Purpose: Establish the persistence layer that all subsequent plans depend on — two new Drizzle tables (chat_conversations, chat_messages), a migration, and shared TypeScript types + Zod validators. Output: Migration SQL applied, tables created, types and validators importable from @paperclipai/shared.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/21-chat-foundation/21-RESEARCH.md From packages/db/src/schema/documents.ts: ```typescript import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; ```From packages/db/src/schema/companies.ts:
export const companies = pgTable("companies", { id: uuid("id").primaryKey().defaultRandom(), ... });
From packages/db/src/schema/agents.ts:
export const agents = pgTable("agents", { id: uuid("id").primaryKey().defaultRandom(), ... });
From packages/shared/src/types/index.ts — re-exports all type modules. From packages/shared/src/validators/index.ts — re-exports all validator modules.
Task 1: Create Drizzle schema files and generate migration packages/db/src/schema/chat_conversations.ts, packages/db/src/schema/chat_messages.ts, packages/db/src/schema/index.ts - packages/db/src/schema/documents.ts (reference pattern for pgTable, timestamps, indexes) - packages/db/src/schema/companies.ts (FK target for companyId) - packages/db/src/schema/agents.ts (FK target for agentId) - packages/db/src/schema/index.ts (current re-exports to extend) Create `packages/db/src/schema/chat_conversations.ts`: ```typescript import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core"; import { companies } from "./companies.js"; import { agents } from "./agents.js";export const chatConversations = pgTable( "chat_conversations", { id: uuid("id").primaryKey().defaultRandom(), companyId: uuid("company_id").notNull().references(() => companies.id), title: text("title"), agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }), pinnedAt: timestamp("pinned_at", { withTimezone: true }), archivedAt: timestamp("archived_at", { withTimezone: true }), deletedAt: timestamp("deleted_at", { withTimezone: true }), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index("chat_conversations_company_updated_idx").on(table.companyId, table.updatedAt), index("chat_conversations_company_deleted_idx").on(table.companyId, table.deletedAt), ], );
Create `packages/db/src/schema/chat_messages.ts`:
```typescript
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
import { chatConversations } from "./chat_conversations.js";
export const chatMessages = pgTable(
"chat_messages",
{
id: uuid("id").primaryKey().defaultRandom(),
conversationId: uuid("conversation_id").notNull()
.references(() => chatConversations.id, { onDelete: "cascade" }),
role: text("role").notNull(),
content: text("content").notNull(),
agentId: uuid("agent_id"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt),
],
);
Add to packages/db/src/schema/index.ts at the end:
export { chatConversations } from "./chat_conversations.js";
export { chatMessages } from "./chat_messages.js";
IMPORTANT: Check the index helper pattern in the existing schema files — Drizzle v0.38 may use the array syntax (table) => [index(...)] instead of the object syntax (table) => ({...}). Match whichever pattern the existing documents.ts or issues.ts uses.
Then run:
cd /opt/nexus && pnpm db:generate
This generates the migration SQL file. Inspect it to confirm it contains CREATE TABLE chat_conversations, CREATE TABLE chat_messages, ON DELETE CASCADE, and the two indexes. Do NOT run pnpm db:migrate — that happens at server start.
cd /opt/nexus && grep -r "chat_conversations" packages/db/src/schema/index.ts && grep -r "chat_messages" packages/db/src/schema/index.ts && ls packages/db/src/migrations/*.sql | tail -1 | xargs grep -l "chat_conversations"
<acceptance_criteria>
- packages/db/src/schema/chat_conversations.ts contains export const chatConversations = pgTable(
- packages/db/src/schema/chat_messages.ts contains export const chatMessages = pgTable(
- packages/db/src/schema/chat_messages.ts contains onDelete: "cascade"
- packages/db/src/schema/index.ts contains export { chatConversations } from "./chat_conversations.js"
- packages/db/src/schema/index.ts contains export { chatMessages } from "./chat_messages.js"
- A migration SQL file exists in packages/db/src/migrations/ containing CREATE TABLE "chat_conversations"
- The migration SQL contains ON DELETE CASCADE
- The migration SQL contains chat_conversations_company_updated_idx
</acceptance_criteria>
Both Drizzle schema files exist, are exported from index.ts, and a migration SQL has been generated containing the correct DDL with FK constraints and indexes.
export interface ChatConversationListItem { id: string; companyId: string; title: string | null; agentId: string | null; pinnedAt: string | null; archivedAt: string | null; updatedAt: string; lastMessagePreview: string | null; }
export interface ChatMessage { id: string; conversationId: string; role: "user" | "assistant" | "system"; content: string; agentId: string | null; createdAt: string; }
export interface ChatConversationListResponse { items: ChatConversationListItem[]; hasMore: boolean; }
export interface ChatMessageListResponse { items: ChatMessage[]; hasMore: boolean; }
Create `packages/shared/src/validators/chat.ts`:
```typescript
import { z } from "zod";
export const createConversationSchema = z.object({
title: z.string().max(200).optional(),
agentId: z.string().uuid().optional(),
});
export const updateConversationSchema = z.object({
title: z.string().max(200).optional(),
agentId: z.string().uuid().nullable().optional(),
pinnedAt: z.string().datetime().nullable().optional(),
archivedAt: z.string().datetime().nullable().optional(),
});
export const createMessageSchema = z.object({
role: z.enum(["user", "assistant", "system"]),
content: z.string().min(1).max(100_000),
agentId: z.string().uuid().optional(),
});
Add to packages/shared/src/types/index.ts:
export * from "./chat.js";
Add to packages/shared/src/validators/index.ts:
export * from "./chat.js";
cd /opt/nexus && npx tsx -e "import { createConversationSchema, createMessageSchema } from '@paperclipai/shared'; console.log('validators OK');" 2>/dev/null || grep -q "createConversationSchema" packages/shared/src/validators/chat.ts && grep -q "ChatConversation" packages/shared/src/types/chat.ts && echo "files OK"
- packages/shared/src/types/chat.ts contains `export interface ChatConversation`
- packages/shared/src/types/chat.ts contains `export interface ChatMessage`
- packages/shared/src/types/chat.ts contains `export interface ChatConversationListItem`
- packages/shared/src/validators/chat.ts contains `export const createConversationSchema`
- packages/shared/src/validators/chat.ts contains `export const updateConversationSchema`
- packages/shared/src/validators/chat.ts contains `export const createMessageSchema`
- packages/shared/src/types/index.ts contains `export * from "./chat.js"`
- packages/shared/src/validators/index.ts contains `export * from "./chat.js"`
Chat types (ChatConversation, ChatMessage, ChatConversationListItem) and Zod validators (createConversationSchema, updateConversationSchema, createMessageSchema) exist and are re-exported from the shared package barrel files.
- Migration SQL file contains CREATE TABLE for both chat_conversations and chat_messages
- Schema files follow existing Drizzle pattern (pgTable, uuid PKs, timestamp with timezone)
- chat_messages FK has ON DELETE CASCADE
- Types and validators importable from @paperclipai/shared
<success_criteria>
- Two new Drizzle schema files created and exported
- Migration SQL generated with correct DDL
- Shared types and Zod validators created and re-exported
- No modifications to existing tables </success_criteria>