# Generate migrationbun drizzle-kit generate# Run migrationbun drizzle-kit migrate
Or create the schema manually:
Copy
CREATE SCHEMA IF NOT EXISTS ff_ai;CREATE TABLE ff_ai.threads ( id BIGSERIAL PRIMARY KEY, public_id TEXT NOT NULL, resource_id TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW() NOT NULL, updated_at TIMESTAMP DEFAULT NOW() NOT NULL, UNIQUE(resource_id, public_id));CREATE TABLE ff_ai.messages ( id BIGSERIAL PRIMARY KEY, uuid UUID NOT NULL UNIQUE, thread_id BIGINT NOT NULL REFERENCES ff_ai.threads(id) ON DELETE CASCADE, ai_sdk_v5 JSONB, created_at TIMESTAMP DEFAULT NOW() NOT NULL);CREATE INDEX idx_messages_thread_id ON ff_ai.messages(thread_id);CREATE INDEX idx_messages_created_at ON ff_ai.messages(created_at);
The provider implements the window size feature efficiently:
Finds the thread by resourceId and publicId
Queries for the N most recent user messages
Identifies the oldest user message in the window
Returns all messages (user, assistant, tool) from that point forward
This ensures you get complete conversation context including all assistant responses and tool interactions.
Copy
-- Step 1: Find recent user messages (window)SELECT id, created_atFROM ff_ai.messagesWHERE thread_id = $1 AND ai_sdk_v5->>'role' = 'user'ORDER BY created_at DESC, id DESCLIMIT $windowSize;-- Step 2: Get all messages from oldest in windowSELECT uuid, ai_sdk_v5, created_atFROM ff_ai.messagesWHERE thread_id = $1 AND id >= $oldestIdORDER BY created_at ASC, id ASC;
-- Delete a conversation and all its messagesDELETE FROM ff_ai.threadsWHERE resource_id = 'user-123' AND public_id = 'thread-456';-- Messages are automatically deleted
-- On messages.thread_id for fast thread lookupsCREATE INDEX idx_messages_thread_id ON ff_ai.messages(thread_id);-- On messages.created_at for window queriesCREATE INDEX idx_messages_created_at ON ff_ai.messages(created_at);
Consider adding:
Copy
-- For user-specific queriesCREATE INDEX idx_threads_resource_id ON ff_ai.threads(resource_id);-- For JSONB queries (if needed)CREATE INDEX idx_messages_role ON ff_ai.messages USING gin ((ai_sdk_v5->'role'));
Connection Pooling
Use connection pooling for better performance:
Copy
const sql = postgres(process.env.DATABASE_URL!, { max: 10, // Maximum pool size idle_timeout: 20, connect_timeout: 10});
JSONB Performance
The aiSdkV5 column stores the complete message as JSONB:
Pros: Flexible schema, easy queries, no migration needed
Cons: Slightly slower than relational columns
For high-volume applications, consider:
Extracting frequently-queried fields to columns
Using JSONB operators for efficient queries
Adding GIN indexes for JSONB queries
Window Size Impact
Larger window sizes require more database queries:
Window size 10: ~50-100 messages typically returned
Window size 50: ~250-500 messages typically returned
Monitor query performance and adjust window sizes accordingly.