import { Low } from "lowdb"; import { JSONFile } from "lowdb/node"; import type { CommittedMessage } from "../types"; import type { Conversation, ConversationEntity, Fact, FactEntity, FactTrigger, FactTriggerEntity, MessageEntity, } from "./common"; import { nanoid } from "nanoid"; type DB = { conversations: Array; messages: Array; facts: Array; factTriggers: Array; }; export const dbClient = 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 dbClient.read(); /** Write the database to the file, in case it didn't exist before. */ await dbClient.write(); const conversations: ConversationEntity = { construct: (conversation: Conversation) => conversation, create: async (_conversation) => { const conversation = { ..._conversation, id: nanoid() }; await dbClient.data.conversations.push(conversation); await dbClient.write(); return conversation; }, createMany: async (_conversations) => { const conversations = _conversations.map((c) => ({ ...c, id: nanoid() })); await dbClient.data.conversations.push(...conversations); await dbClient.write(); return conversations; }, findAll: async () => { return dbClient.data.conversations; }, findById: async (id) => { return dbClient.data.conversations.find((c) => c.id === id); }, update: async (id, data: Partial) => { const conversationIndex = dbClient.data.conversations.findIndex( (c) => c.id === id ); if (conversationIndex === -1) throw new Error("Conversation not found"); dbClient.data.conversations[conversationIndex] = { ...dbClient.data.conversations[conversationIndex], ...data, }; await dbClient.write(); }, delete: async (id) => { dbClient.data.conversations.splice( dbClient.data.conversations.findIndex((c) => c.id === id), 1 ); const deletedMessageIds = dbClient.data.messages .filter((m) => m.conversationId === id) .map((m) => m.id); dbClient.data.messages = dbClient.data.messages.filter( (m) => m.conversationId !== id ); const deletedFactIds = dbClient.data.facts .filter((fact) => deletedMessageIds.includes(fact.sourceMessageId)) .map((fact) => fact.id); dbClient.data.facts = dbClient.data.facts.filter( (fact) => !deletedFactIds.includes(fact.id) ); dbClient.data.factTriggers = dbClient.data.factTriggers.filter( (factTrigger) => !deletedFactIds.includes(factTrigger.sourceFactId) ); await dbClient.write(); }, fetchMessages: async (conversationId) => { const rows = await dbClient.data.messages.filter( (m) => m.conversationId === conversationId ); return rows as Array; }, }; const factTriggers: FactTriggerEntity = { construct: (factTrigger: FactTrigger) => factTrigger, create: async (_factTrigger) => { const factTrigger = { ..._factTrigger, id: nanoid() }; await dbClient.data.factTriggers.push(factTrigger); await dbClient.write(); return factTrigger; }, createMany: async (_factTriggers) => { const factTriggers = _factTriggers.map((f) => ({ ...f, id: nanoid() })); await dbClient.data.factTriggers.push(...factTriggers); await dbClient.write(); return factTriggers; }, findAll: async () => { return dbClient.data.factTriggers; }, findById: async (id) => { return dbClient.data.factTriggers.find( (factTrigger) => factTrigger.id === id ); }, findByConversationId: async (conversationId) => { const messageIds = await dbClient.data.messages .filter((m) => m.conversationId === conversationId) .map((m) => m.id); const factIds = dbClient.data.facts .filter((fact) => messageIds.includes(fact.sourceMessageId)) .map((fact) => fact.id); return dbClient.data.factTriggers.filter((factTrigger) => factIds.includes(factTrigger.sourceFactId) ); }, update: async (id, data: Partial) => { const factTriggerIndex = dbClient.data.factTriggers.findIndex( (factTrigger) => factTrigger.id === id ); if (factTriggerIndex === -1) throw new Error("Fact trigger not found"); dbClient.data.factTriggers[factTriggerIndex] = { ...dbClient.data.factTriggers[factTriggerIndex], ...data, }; await dbClient.write(); }, delete: async (id) => { const deletedFactTriggerIndex = dbClient.data.factTriggers.findIndex( (factTrigger) => factTrigger.id === id ); if (deletedFactTriggerIndex === -1) throw new Error("Fact trigger not found"); dbClient.data.factTriggers.splice(deletedFactTriggerIndex, 1); await dbClient.write(); }, findByFactId: async (factId: string) => { return dbClient.data.factTriggers.filter( (factTrigger) => factTrigger.sourceFactId === factId ); }, }; const facts: FactEntity = { construct: (fact: Fact) => fact, create: async (_fact) => { const fact = { ..._fact, id: nanoid() }; await dbClient.data.facts.push(fact); await dbClient.write(); return fact; }, createMany: async (_facts) => { const facts = _facts.map((f) => ({ ...f, id: nanoid() })); await dbClient.data.facts.push(...facts); await dbClient.write(); return facts; }, findAll: async () => { return dbClient.data.facts; }, findById: async (id) => { return dbClient.data.facts.find((fact) => fact.id === id); }, update: async (id, data: Partial) => { const factIndex = dbClient.data.facts.findIndex((fact) => fact.id === id); if (factIndex === -1) throw new Error("Fact not found"); dbClient.data.facts[factIndex] = { ...dbClient.data.facts[factIndex], ...data, }; await dbClient.write(); }, delete: async (id) => { const deletedFactId = dbClient.data.facts.findIndex( (fact) => fact.id === id ); if (deletedFactId === -1) throw new Error("Fact not found"); dbClient.data.facts.splice(deletedFactId, 1); await dbClient.write(); }, findByConversationId: async (conversationId) => { const conversationMessageIds = dbClient.data.messages .filter((m) => m.conversationId === conversationId) .map((m) => m.id); const rows = await dbClient.data.facts.filter((f) => conversationMessageIds.includes(f.sourceMessageId) ); return rows as Array; }, }; const messages: MessageEntity = { construct: (message: CommittedMessage) => message, create: async (_message) => { const message = { ..._message, id: nanoid() }; await dbClient.data.messages.push(message); await dbClient.write(); return message; }, createMany: async (_messages) => { const messages = _messages.map((m) => ({ ...m, id: nanoid() })); await dbClient.data.messages.push(...messages); await dbClient.write(); return messages; }, findAll: async () => { return dbClient.data.messages; }, findById: async (id) => { return dbClient.data.messages.find((m) => m.id === id); }, update: async (id, data: Partial) => { const messageIndex = dbClient.data.messages.findIndex((m) => m.id === id); if (messageIndex === -1) throw new Error("Message not found"); dbClient.data.messages[messageIndex] = { ...dbClient.data.messages[messageIndex], ...data, }; await dbClient.write(); }, delete: async (id) => { dbClient.data.messages.splice( dbClient.data.messages.findIndex((m) => m.id === id), 1 ); await dbClient.write(); }, findByConversationId: async (conversationId) => { return dbClient.data.messages.filter( (m) => m.conversationId === conversationId ); }, }; export const db = { conversations, factTriggers, facts, messages, };