import { Low } from "lowdb"; import { JSONFile } from "lowdb/node"; import type { CommittedMessage } from "../types"; import type { Entity } from "./common"; import { nanoid } from "nanoid"; export type Conversation = { id: string; title: string; userId: string; }; export type Fact = { id: string; userId: string; sourceMessageId: string; content: string; createdAt: string; }; export type FactTrigger = { id: string; sourceFactId: string; content: string; priorityMultiplier: number; priorityMultiplierReason: string; scopeConversationId: string; createdAt: string; }; type DB = { conversations: Array; messages: Array; facts: Array; factTriggers: Array; }; export const db = new Low(new JSONFile("db.json"), { conversations: [], messages: [], facts: [], factTriggers: [], }); /** Initialize the database. Sets `db.data` to the default state if the file doesn't exist. */ await db.read(); /** Write the database to the file, in case it didn't exist before. */ await db.write(); const conversations: Entity & { fetchMessages: (conversationId: string) => Promise>; } = { construct: (conversation: Conversation) => conversation, create: async (conversation: Conversation) => { conversation.id = conversation.id ?? nanoid(); await db.data.conversations.push(conversation); await db.write(); return conversation; }, createMany: async (conversations: Array) => { await db.data.conversations.push(...conversations); await db.write(); return conversations; }, findAll: async () => { return db.data.conversations; }, findById: async (id) => { return db.data.conversations.find((c) => c.id === id); }, update: async (id, data: Partial) => { const conversationIndex = db.data.conversations.findIndex( (c) => c.id === id ); if (conversationIndex === -1) throw new Error("Conversation not found"); db.data.conversations[conversationIndex] = { ...db.data.conversations[conversationIndex], ...data, }; await db.write(); }, delete: async (id) => { db.data.conversations.splice( db.data.conversations.findIndex((c) => c.id === id), 1 ); const deletedMessageIds = db.data.messages .filter((m) => m.conversationId === id) .map((m) => m.id); db.data.messages = db.data.messages.filter((m) => m.conversationId !== id); const deletedFactIds = db.data.facts .filter((fact) => deletedMessageIds.includes(fact.sourceMessageId)) .map((fact) => fact.id); db.data.facts = db.data.facts.filter( (fact) => !deletedFactIds.includes(fact.id) ); db.data.factTriggers = db.data.factTriggers.filter( (factTrigger) => !deletedFactIds.includes(factTrigger.sourceFactId) ); await db.write(); }, fetchMessages: async (conversationId) => { const rows = await db.data.messages.filter( (m) => m.conversationId === conversationId ); return rows as Array; }, }; const factTriggers: Entity & { findByFactId: (factId: string) => Promise>; } = { construct: (factTrigger: FactTrigger) => factTrigger, create: async (factTrigger: FactTrigger) => { factTrigger.id = factTrigger.id ?? nanoid(); await db.data.factTriggers.push(factTrigger); await db.write(); return factTrigger; }, createMany: async (factTriggers: Array) => { await db.data.factTriggers.push(...factTriggers); await db.write(); return factTriggers; }, findAll: async () => { return db.data.factTriggers; }, findById: async (id) => { return db.data.factTriggers.find((factTrigger) => factTrigger.id === id); }, update: async (id, data: Partial) => { const factTriggerIndex = db.data.factTriggers.findIndex( (factTrigger) => factTrigger.id === id ); if (factTriggerIndex === -1) throw new Error("Fact trigger not found"); db.data.factTriggers[factTriggerIndex] = { ...db.data.factTriggers[factTriggerIndex], ...data, }; await db.write(); }, delete: async (id) => { const deletedFactTriggerIndex = db.data.factTriggers.findIndex( (factTrigger) => factTrigger.id === id ); if (deletedFactTriggerIndex === -1) throw new Error("Fact trigger not found"); db.data.factTriggers.splice(deletedFactTriggerIndex, 1); await db.write(); }, findByFactId: async (factId: string) => { return db.data.factTriggers.filter( (factTrigger) => factTrigger.sourceFactId === factId ); }, }; const facts: Entity & { findByConversationId: (conversationId: string) => Promise>; } = { construct: (fact: Fact) => fact, create: async (fact: Fact) => { fact.id = fact.id ?? nanoid(); await db.data.facts.push(fact); await db.write(); return fact; }, createMany: async (facts: Array) => { await db.data.facts.push(...facts); await db.write(); return facts; }, findAll: async () => { return db.data.facts; }, findById: async (id) => { return db.data.facts.find((fact) => fact.id === id); }, update: async (id, data: Partial) => { const factIndex = db.data.facts.findIndex((fact) => fact.id === id); if (factIndex === -1) throw new Error("Fact not found"); db.data.facts[factIndex] = { ...db.data.facts[factIndex], ...data, }; await db.write(); }, delete: async (id) => { const deletedFactId = db.data.facts.findIndex((fact) => fact.id === id); if (deletedFactId === -1) throw new Error("Fact not found"); db.data.facts.splice(deletedFactId, 1); await db.write(); }, findByConversationId: async (conversationId) => { const conversationMessageIds = db.data.messages .filter((m) => m.conversationId === conversationId) .map((m) => m.id); const rows = await db.data.facts.filter((f) => conversationMessageIds.includes(f.sourceMessageId) ); return rows as Array; }, }; const messages: Entity & { findByConversationId: ( conversationId: string ) => Promise>; } = { construct: (message: CommittedMessage) => message, create: async (message: CommittedMessage) => { message.id = message.id ?? nanoid(); await db.data.messages.push(message); await db.write(); return message; }, createMany: async (messages: Array) => { await db.data.messages.push(...messages); await db.write(); return messages; }, findAll: async () => { return db.data.messages; }, findById: async (id) => { return db.data.messages.find((m) => m.id === id); }, update: async (id, data: Partial) => { const messageIndex = db.data.messages.findIndex((m) => m.id === id); if (messageIndex === -1) throw new Error("Message not found"); db.data.messages[messageIndex] = { ...db.data.messages[messageIndex], ...data, }; await db.write(); }, delete: async (id) => { db.data.messages.splice( db.data.messages.findIndex((m) => m.id === id), 1 ); await db.write(); }, findByConversationId: async (conversationId) => { return db.data.messages.filter((m) => m.conversationId === conversationId); }, }; export const _db = { conversations, factTriggers, facts, messages, };