cherry-studio/src/main/data/db/schemas/message.ts
fullex 1b9d8fe24a feat(database): add user data schemas for topic, message, group, and tag
- Add topicTable schema with group organization and pinning support
  - Add messageTable schema with tree structure (adjacency list pattern)
  - Add groupTable schema for organizing entities by type
  - Add tagTable and entityTagTable schemas for tagging system
  - Add FTS5 full-text search support for message content
  - Update preferenceTable to use composite primary key (scope, key)
  - Regenerate initial migration with all tables

  Changes Summary

  | Type        | Files                                                               |
  |-------------|---------------------------------------------------------------------|
  | New schemas | topic.ts, message.ts, group.ts, tag.ts, entityTag.ts, messageFts.ts |
  | Modified    | preference.ts (index → composite PK)                                |
  | Migration   | Renamed 0000_solid_lord_hawal.sql → 0000_init.sql with all tables   |

  This is part of the data refactoring project - adding core user data table schemas.
2025-12-24 23:19:25 +08:00

61 lines
2.2 KiB
TypeScript

import { sql } from 'drizzle-orm'
import { check, index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { createUpdateDeleteTimestamps } from './columnHelpers'
import { topicTable } from './topic'
/**
* Message table - stores chat messages with tree structure
*
* Uses adjacency list pattern (parentId) for tree navigation.
* Block content is stored as JSON in the data field.
* searchableText is a generated column for FTS5 indexing.
*/
export const messageTable = sqliteTable(
'message',
{
id: text().primaryKey(),
// FK to topic - CASCADE: delete messages when topic is deleted
topicId: text()
.notNull()
.references(() => topicTable.id, { onDelete: 'cascade' }),
// Adjacency list parent reference for tree structure
// SET NULL: preserve child messages when parent is deleted
parentId: text().references(() => messageTable.id, { onDelete: 'set null' }),
// Group ID for multi-model responses (0 = normal branch)
responseGroupId: integer().default(0),
// Message role: user, assistant, system
role: text().notNull(),
// Final status: SUCCESS, ERROR, PAUSED
status: text().notNull(),
// FK to assistant
assistantId: text(),
// Preserved assistant info for display
assistantMeta: text({ mode: 'json' }),
// Model identifier
modelId: text(),
// Preserved model info (provider, name)
modelMeta: text({ mode: 'json' }),
// Main content - contains blocks[], mentions, etc.
data: text({ mode: 'json' }).notNull(),
// Token usage statistics
usage: text({ mode: 'json' }),
// Performance metrics
metrics: text({ mode: 'json' }),
// Trace ID for tracking
traceId: text(),
// Searchable text extracted from data.blocks (populated by trigger, used for FTS5)
searchableText: text(),
...createUpdateDeleteTimestamps
},
(t) => [
// Indexes
index('message_parent_id_idx').on(t.parentId),
index('message_topic_created_idx').on(t.topicId, t.createdAt),
index('message_trace_id_idx').on(t.traceId),
// Check constraints for enum fields
check('message_role_check', sql`${t.role} IN ('user', 'assistant', 'system')`),
check('message_status_check', sql`${t.status} IN ('success', 'error', 'paused')`)
]
)