import { sql } from "drizzle-orm"; import { bigint, check, foreignKey, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uniqueIndex, uuid, varchar, } from "drizzle-orm/pg-core"; import { workspaceMembers } from "./objects"; import { workspaces } from "./workspaces"; export const chatChannels = pgTable( "chat_channels", { id: uuid("id").primaryKey().defaultRandom(), workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), kind: varchar("kind", { length: 16 }) .notNull() .$type<"public" | "private" | "direct">(), name: varchar("name", { length: 200 }), ownerUserId: uuid("owner_user_id").notNull(), directKey: text("direct_key"), nextSequence: bigint("next_sequence", { mode: "bigint" }) .notNull() .default(sql`1`), nextMutationSequence: bigint("next_mutation_sequence", { mode: "bigint" }) .notNull() .default(sql`1`), /** Structural signal; transaction policy must reject writes when this is non-null. */ archivedAt: timestamp("archived_at", { withTimezone: true }), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), updatedAt: timestamp("updated_at", { withTimezone: true }) .defaultNow() .notNull(), }, (table) => ({ workspaceIdx: index("chat_channels_workspace_id_idx").on(table.workspaceId), workspaceIdUnique: unique("chat_channels_workspace_id_id_unique").on( table.workspaceId, table.id, ), ownerFk: foreignKey({ columns: [table.workspaceId, table.ownerUserId], foreignColumns: [workspaceMembers.workspaceId, workspaceMembers.userId], name: "chat_channels_owner_member_fk", }), directKeyUnique: uniqueIndex("chat_channels_workspace_id_direct_key_unique") .on(table.workspaceId, table.directKey) .where(sql`${table.directKey} is not null`), kindCheck: check( "chat_channels_kind_check", sql`${table.kind} in ('public', 'private', 'direct')`, ), directKeyCheck: check( "chat_channels_direct_key_check", sql`(${table.kind} = 'direct' and ${table.directKey} is not null and ${table.name} is null) or (${table.kind} <> 'direct' and ${table.directKey} is null)`, ), sequenceCheck: check( "chat_channels_next_sequence_check", sql`${table.nextSequence} > 0`, ), }), ); export const chatChannelMembers = pgTable( "chat_channel_members", { workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), channelId: uuid("channel_id").notNull(), userId: uuid("user_id").notNull(), joinedAt: timestamp("joined_at", { withTimezone: true }) .defaultNow() .notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.workspaceId, table.channelId, table.userId], }), workspaceIdx: index("chat_channel_members_workspace_id_idx").on( table.workspaceId, ), userIdx: index("chat_channel_members_workspace_id_user_id_idx").on( table.workspaceId, table.userId, ), channelFk: foreignKey({ columns: [table.workspaceId, table.channelId], foreignColumns: [chatChannels.workspaceId, chatChannels.id], name: "chat_channel_members_channel_fk", }).onDelete("cascade"), memberFk: foreignKey({ columns: [table.workspaceId, table.userId], foreignColumns: [workspaceMembers.workspaceId, workspaceMembers.userId], name: "chat_channel_members_workspace_member_fk", }), }), ); export const chatMessages = pgTable( "chat_messages", { id: uuid("id").primaryKey().defaultRandom(), workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), channelId: uuid("channel_id").notNull(), sequence: bigint("sequence", { mode: "bigint" }).notNull(), parentMessageId: uuid("parent_message_id"), authorType: varchar("author_type", { length: 16 }) .notNull() .$type<"human" | "agent" | "system">(), authorUserId: uuid("author_user_id"), /** Opaque, pinned agent identity until the agent registry schema exists. */ authorAgentId: uuid("author_agent_id"), body: text("body").notNull(), /** Pinned send intent survives edits and tombstones. */ sendIntentFingerprint: text("send_intent_fingerprint"), revision: integer("revision").notNull().default(1), status: varchar("status", { length: 16 }) .notNull() .default("sent") .$type<"sent" | "edited" | "deleted">(), clientIdempotencyKey: varchar("client_idempotency_key", { length: 200, }).notNull(), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), editedAt: timestamp("edited_at", { withTimezone: true }), deletedAt: timestamp("deleted_at", { withTimezone: true }), }, (table) => ({ workspaceIdx: index("chat_messages_workspace_id_idx").on(table.workspaceId), revisionCheck: check( "chat_messages_revision_check", sql`${table.revision} > 0`, ), workspaceIdUnique: unique("chat_messages_workspace_id_id_unique").on( table.workspaceId, table.id, ), workspaceChannelIdUnique: unique( "chat_messages_workspace_channel_id_unique", ).on(table.workspaceId, table.channelId, table.id), channelSequenceUnique: unique( "chat_messages_workspace_channel_sequence_unique", ).on(table.workspaceId, table.channelId, table.sequence), clientKeyUnique: unique( "chat_messages_workspace_channel_client_key_unique", ).on(table.workspaceId, table.channelId, table.clientIdempotencyKey), channelFk: foreignKey({ columns: [table.workspaceId, table.channelId], foreignColumns: [chatChannels.workspaceId, chatChannels.id], name: "chat_messages_channel_fk", }).onDelete("cascade"), parentFk: foreignKey({ columns: [table.workspaceId, table.channelId, table.parentMessageId], foreignColumns: [table.workspaceId, table.channelId, table.id], name: "chat_messages_parent_fk", }), humanAuthorFk: foreignKey({ columns: [table.workspaceId, table.authorUserId], foreignColumns: [workspaceMembers.workspaceId, workspaceMembers.userId], name: "chat_messages_human_author_member_fk", }), authorCheck: check( "chat_messages_author_check", sql`(${table.authorType} = 'human' and ${table.authorUserId} is not null and ${table.authorAgentId} is null) or (${table.authorType} = 'agent' and ${table.authorUserId} is null and ${table.authorAgentId} is not null) or (${table.authorType} = 'system' and ${table.authorUserId} is null and ${table.authorAgentId} is null)`, ), statusCheck: check( "chat_messages_status_check", sql`${table.status} in ('sent', 'edited', 'deleted')`, ), sequenceCheck: check( "chat_messages_sequence_check", sql`${table.sequence} >= 0`, ), }), ); export const chatMessageMentions = pgTable( "chat_message_mentions", { workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), messageId: uuid("message_id").notNull(), userId: uuid("user_id").notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.workspaceId, table.messageId, table.userId], }), workspaceIdx: index("chat_message_mentions_workspace_id_idx").on( table.workspaceId, ), messageFk: foreignKey({ columns: [table.workspaceId, table.messageId], foreignColumns: [chatMessages.workspaceId, chatMessages.id], name: "chat_message_mentions_message_fk", }).onDelete("cascade"), mentionedMemberFk: foreignKey({ columns: [table.workspaceId, table.userId], foreignColumns: [workspaceMembers.workspaceId, workspaceMembers.userId], name: "chat_message_mentions_workspace_member_fk", }), }), ); export const chatMessageMutations = pgTable( "chat_message_mutations", { id: uuid("id").primaryKey().defaultRandom(), workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), channelId: uuid("channel_id").notNull(), messageId: uuid("message_id").notNull(), mutationSequence: bigint("mutation_sequence", { mode: "bigint" }).notNull(), revision: integer("revision").notNull(), kind: varchar("kind", { length: 16 }) .notNull() .$type<"edited" | "deleted">(), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), }, (table) => ({ workspaceIdx: index("chat_message_mutations_workspace_id_idx").on( table.workspaceId, ), sequenceUnique: unique("chat_message_mutations_channel_sequence_unique").on( table.workspaceId, table.channelId, table.mutationSequence, ), channelFk: foreignKey({ columns: [table.workspaceId, table.channelId], foreignColumns: [chatChannels.workspaceId, chatChannels.id], name: "chat_message_mutations_channel_fk", }).onDelete("cascade"), messageFk: foreignKey({ columns: [table.workspaceId, table.messageId], foreignColumns: [chatMessages.workspaceId, chatMessages.id], name: "chat_message_mutations_message_fk", }).onDelete("cascade"), revisionCheck: check( "chat_message_mutations_revision_check", sql`${table.revision} > 1`, ), }), ); export const chatReadCursors = pgTable( "chat_read_cursors", { workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), channelId: uuid("channel_id").notNull(), userId: uuid("user_id").notNull(), sequence: bigint("sequence", { mode: "bigint" }) .notNull() .default(sql`0`), updatedAt: timestamp("updated_at", { withTimezone: true }) .defaultNow() .notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.workspaceId, table.channelId, table.userId], }), workspaceIdx: index("chat_read_cursors_workspace_id_idx").on( table.workspaceId, ), channelFk: foreignKey({ columns: [table.workspaceId, table.channelId], foreignColumns: [chatChannels.workspaceId, chatChannels.id], name: "chat_read_cursors_channel_fk", }).onDelete("cascade"), readerMemberFk: foreignKey({ columns: [table.workspaceId, table.userId], foreignColumns: [workspaceMembers.workspaceId, workspaceMembers.userId], name: "chat_read_cursors_workspace_member_fk", }), sequenceCheck: check( "chat_read_cursors_sequence_check", sql`${table.sequence} >= 0`, ), }), ); export const chatMutationReceipts = pgTable( "chat_mutation_receipts", { workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), actorUserId: uuid("actor_user_id").notNull(), clientMutationId: uuid("client_mutation_id").notNull(), operation: varchar("operation", { length: 50 }).notNull(), fingerprint: text("fingerprint").notNull(), result: jsonb("result").$type>().notNull(), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.workspaceId, table.actorUserId, table.clientMutationId], }), workspaceIdx: index("chat_mutation_receipts_workspace_id_idx").on( table.workspaceId, ), actorFk: foreignKey({ columns: [table.workspaceId, table.actorUserId], foreignColumns: [workspaceMembers.workspaceId, workspaceMembers.userId], name: "chat_mutation_receipts_workspace_member_fk", }).onDelete("cascade"), }), ); export const chatOutbox = pgTable( "chat_outbox", { id: uuid("id").primaryKey().defaultRandom(), workspaceId: uuid("workspace_id") .notNull() .references(() => workspaces.id, { onDelete: "cascade" }), channelId: uuid("channel_id").notNull(), messageId: uuid("message_id"), eventVersion: integer("event_version").notNull().default(1), eventType: varchar("event_type", { length: 100 }).notNull(), dedupeKey: varchar("dedupe_key", { length: 250 }).notNull(), payload: jsonb("payload").$type>().notNull(), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() .notNull(), publishedAt: timestamp("published_at", { withTimezone: true }), attempts: integer("attempts").notNull().default(0), lastError: text("last_error"), }, (table) => ({ workspaceIdx: index("chat_outbox_workspace_id_idx").on(table.workspaceId), unpublishedIdx: index("chat_outbox_unpublished_idx") .on(table.createdAt) .where(sql`${table.publishedAt} is null`), dedupeUnique: unique("chat_outbox_workspace_id_dedupe_key_unique").on( table.workspaceId, table.dedupeKey, ), channelFk: foreignKey({ columns: [table.workspaceId, table.channelId], foreignColumns: [chatChannels.workspaceId, chatChannels.id], name: "chat_outbox_channel_fk", }).onDelete("cascade"), messageFk: foreignKey({ columns: [table.workspaceId, table.messageId], foreignColumns: [chatMessages.workspaceId, chatMessages.id], name: "chat_outbox_message_fk", }).onDelete("cascade"), versionCheck: check( "chat_outbox_event_version_check", sql`${table.eventVersion} > 0`, ), attemptsCheck: check( "chat_outbox_attempts_check", sql`${table.attempts} >= 0`, ), }), );