feat: ditch triplit, move to postgresql + drizzle orm

This commit is contained in:
Zoe
2026-04-08 17:03:07 -05:00
parent c341c96798
commit e2e3ac6e86
121 changed files with 6680 additions and 4373 deletions
@@ -0,0 +1,2 @@
-- Custom SQL migration file, put your code below! --
CREATE EXTENSION IF NOT EXISTS vector;
+95
View File
@@ -0,0 +1,95 @@
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
sessions: r.many.sessions(),
accounts: r.many.accounts(),
},
sessions: {
users: r.one.users({
from: r.sessions.userId,
to: r.users.id
}),
},
accounts: {
users: r.one.users({
from: r.accounts.userId,
to: r.users.id
}),
},
agents: {
topics: r.many.topics({
from: r.agents.id,
to: r.topics.agentId
}),
},
topics: {
messages: r.many.messages({
from: r.topics.id,
to: r.messages.topicId
}),
generations: r.many.generations({
from: r.topics.id,
to: r.generations.topicId
}),
agent: r.one.agents({
from: r.topics.agentId,
to: r.agents.id,
optional: false,
}),
},
messages: {
topic: r.one.topics({
from: r.messages.topicId,
to: r.topics.id,
optional: false,
}),
parts: r.many.messageParts({
from: r.messages.id,
to: r.messageParts.messageId
}),
attachments: r.many.attachments({
from: r.messages.id,
to: r.attachments.messageId
}),
generation: r.one.generations({
from: r.messages.generationId,
to: r.generations.id
}),
parent: r.one.messages({
from: r.messages.parentMessageId,
to: r.messages.id
}),
children: r.many.messages({
from: r.messages.id,
to: r.messages.parentMessageId
}),
},
attachments: {
file: r.one.files({
from: r.attachments.fileId,
to: r.files.id,
optional: false,
}),
},
messageParts: {
toolCall: r.one.toolCalls({
from: r.messageParts.toolCallId,
to: r.toolCalls.id
}),
},
models: {
provider: r.one.providers({
from: r.models.providerId,
to: r.providers.id,
optional: false,
}),
},
providers: {
models: r.many.models({
from: r.providers.id,
to: r.models.providerId
}),
},
}))
+298
View File
@@ -0,0 +1,298 @@
import {
pgTable,
text,
timestamp,
boolean,
integer,
jsonb,
pgEnum,
vector,
index
} from 'drizzle-orm/pg-core';
import { nanoid } from 'nanoid';
export const roleEnum = pgEnum('role', ['user', 'assistant']);
export const partTypeEnum = pgEnum('part_type', ['reasoning', 'text', 'tool-call', 'file']);
export const statusEnum = pgEnum('status', ['pending', 'completed', 'failed', 'cancelled']);
export enum ToolCallType {
Text = 'text',
Json = 'json',
}
export const settings = pgTable('settings', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
systemAssistants: jsonb('system_assistants').$type<Record<string, any>>().notNull().default({}),
appearance: jsonb('appearance').$type<{
colorScheme?: 'light' | 'dark' | 'system';
accent?: string;
neutral?: string;
hinting?: number;
fontSize?: string;
}>(),
}, (table) => [
index('settings_userId_idx').on(table.userId),
]);
export const providers = pgTable('providers', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
type: text('type').notNull(),
name: text('name').notNull(),
enabled: boolean('enabled').notNull().default(true),
config: jsonb('config').notNull().$type<{
apiKey?: string;
apiProxyUrl?: string;
}>().default({}),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
}, (table) => [
index('providers_userId_idx').on(table.userId),
]);
export const models = pgTable('models', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
externalId: text('external_id').notNull(),
providerId: text('provider_id').notNull().references(() => providers.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
cost: jsonb('cost').notNull().$type<{
prompt?: string;
completion?: string;
request?: string;
image?: string;
imageTokens?: string;
imageOutput?: string;
audio?: string;
audioOutput?: string;
inputAudioCache?: string;
webSearch?: string;
internalReasoning?: string;
inputCacheRead?: string;
inputCacheWrite?: string;
discount?: string;
}>().default({}),
inputModalities: text('input_modalities').array().notNull().default([]),
outputModalities: text('output_modalities').array().notNull().default([]),
capabilities: text('capabilities').array().notNull().default([]),
contextWindow: integer('context_window'),
supportedParameters: text('supported_parameters').array(),
isCustom: boolean('is_custom').notNull().default(false),
enabled: boolean('enabled').notNull().default(true),
releasedAt: timestamp('released_at'),
}, (table) => [
index('models_providerId_idx').on(table.providerId),
index('models_userId_idx').on(table.userId),
]);
export const agents = pgTable('agents', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
name: text('name').notNull(),
systemPrompt: text('system_prompt'),
imageUrl: text('image_url'),
defaultModelId: text('default_model_id').references(() => models.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => [
index('agents_userId_idx').on(table.userId),
]);
export const topics = pgTable('topics', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
agentId: text('agent_id').notNull().references(() => agents.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
renaming: boolean('renaming').default(false),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => [
index('topics_agentId_idx').on(table.agentId),
index('topics_userId_idx').on(table.userId),
]);
export const messages = pgTable('messages', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
topicId: text('topic_id').notNull().references(() => topics.id, { onDelete: 'cascade' }),
parentMessageId: text('parent_message_id'),
generationId: text('generation_id').references(() => generations.id, { onDelete: 'cascade' }),
role: roleEnum('role').notNull(),
content: text('content'),
focusedIndex: integer('focused_index'),
deleted: boolean('deleted').default(false),
updatedAt: timestamp('updated_at').notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => [
index('messages_topicId_idx').on(table.topicId),
index('messages_parentMessageId_idx').on(table.parentMessageId),
index('messages_userId_idx').on(table.userId),
]);
export const messageParts = pgTable('message_parts', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
topicId: text('topic_id').notNull().references(() => topics.id, { onDelete: 'cascade' }),
messageId: text('message_id').notNull().references(() => messages.id, { onDelete: 'cascade' }),
toolCallId: text('tool_call_id').references(() => toolCalls.id, { onDelete: 'cascade' }),
type: partTypeEnum('type').notNull(),
content: text('content'),
providerOptions: jsonb('provider_options'),
finished: boolean('finished').notNull().default(false),
createdAt: timestamp('created_at').notNull().defaultNow(),
lastUpdatedAt: timestamp('last_updated_at').notNull().defaultNow(),
}, (table) => [
index('message_parts_topicId_idx').on(table.topicId),
index('message_parts_messageId_idx').on(table.messageId),
index('message_parts_userId_idx').on(table.userId),
]);
export const toolCalls = pgTable('tool_calls', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
toolName: text('tool_name').notNull(),
status: statusEnum('status').notNull().default('pending'),
input: jsonb('input').$type<{
type: ToolCallType;
value: string;
}>(),
output: jsonb('output').$type<{
type: ToolCallType;
value: string;
}>(),
error: jsonb('error').$type<{
type: ToolCallType;
value: string;
}>(),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => [
index('tool_calls_userId_idx').on(table.userId),
]);
export const generations = pgTable('generations', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
topicId: text('topic_id').notNull().references(() => topics.id, { onDelete: 'cascade' }),
modelId: text('model_id').notNull(),
status: statusEnum('status').notNull(),
tokens: jsonb('tokens').$type<{
input?: number;
cache?: {
read?: number;
write?: number;
};
output?: number;
thinking?: number;
ttft?: number;
tps?: number;
}>(),
error: text('error'),
}, (table) => [
index('generations_topicId_idx').on(table.topicId),
index('generations_userId_idx').on(table.userId),
]);
export const files = pgTable('files', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
name: text('name').notNull(),
mimeType: text('mime_type').notNull(),
url: text('url').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => [
index('files_userId_idx').on(table.userId),
]);
export const embeddings = pgTable('embeddings', {
id: text('id').primaryKey().$defaultFn(nanoid),
fileId: text('file_id').notNull().references(() => files.id, { onDelete: 'cascade' }),
content: text('content').notNull(),
embedding: vector('embedding', { dimensions: 1536 }),
metadata: jsonb('metadata'),
}, (table) => [
index('embeddings_fileId_idx').on(table.fileId),
]);
export const attachments = pgTable('attachments', {
id: text('id').primaryKey().$defaultFn(nanoid),
userId: text('user_id').notNull(),
topicId: text('topic_id').notNull().references(() => topics.id),
messageId: text('message_id').notNull().references(() => messages.id, { onDelete: 'cascade' }),
fileId: text('file_id').notNull().references(() => files.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').notNull().defaultNow(),
}, (table) => [
index('attachments_topicId_idx').on(table.topicId),
index('attachments_messageId_idx').on(table.messageId),
index('attachments_userId_idx').on(table.userId),
]);
export const users = pgTable("users", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const sessions = pgTable(
"sessions",
{
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
},
(table) => [index("sessions_userId_idx").on(table.userId)],
);
export const accounts = pgTable(
"accounts",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("accounts_userId_idx").on(table.userId)],
);
export const verifications = pgTable(
"verifications",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
},
(table) => [index("verifications_identifier_idx").on(table.identifier)],
);