metatron-open-seo/src/db/sam.schema.ts

62 lines
2.4 KiB
TypeScript

import { sqliteTable, text, index, primaryKey } from "drizzle-orm/sqlite-core";
import { sql } from "drizzle-orm";
import { user } from "./better-auth-schema";
import { projects } from "./app.schema";
// One row per SAM chat session. The conversation history itself lives in the
// SamChatAgent Durable Object's SQLite (keyed by this id); this table is the
// listable registry the session side-panel reads from, and the project/user
// scoping the Worker authorizes a connection against before it reaches the DO.
// Normalized on purpose: org and user email are derived from the project and
// user rows at read time, never snapshotted here.
export const samSessions = sqliteTable(
"sam_sessions",
{
id: text("id").primaryKey(),
projectId: text("project_id")
.notNull()
.references(() => projects.id, { onDelete: "cascade" }),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
title: text("title").notNull().default("New chat"),
createdAt: text("created_at")
.notNull()
.default(sql`(current_timestamp)`),
updatedAt: text("updated_at")
.notNull()
.default(sql`(current_timestamp)`),
// Soft-delete marker: null = active. Archived sessions disappear from the
// list but keep their registry row and DO transcript for a future unarchive.
archivedAt: text("archived_at"),
},
(table) => [
// The side-panel lists a project's sessions newest-first.
index("sam_sessions_project_updated_idx").on(
table.projectId,
table.updatedAt,
),
],
);
// SAM's persistent project memory: one row per (project, context-block label).
// The SamChatAgent DO surfaces these rows to the model as writable context
// blocks ("memory", "research_log"), so every chat session in a project reads
// and writes the same memory. Lives in the app DB rather than DO storage so it
// is shared across the per-session DOs and stays queryable by the Worker (for
// a future settings/inspection UI).
export const samProjectMemory = sqliteTable(
"sam_project_memory",
{
projectId: text("project_id")
.notNull()
.references(() => projects.id, { onDelete: "cascade" }),
label: text("label").notNull(),
content: text("content").notNull(),
updatedAt: text("updated_at")
.notNull()
.default(sql`(current_timestamp)`),
},
(table) => [primaryKey({ columns: [table.projectId, table.label] })],
);