You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
7.6 KiB
TypeScript

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<Conversation>;
messages: Array</*{
id: string;
conversationId: string;
content: string;
role: "user" | "assistant" | "system" | "data";
index: number;
createdAt: string;
runningSummary?: string;
}*/ CommittedMessage>;
facts: Array<Fact>;
factTriggers: Array<FactTrigger>;
};
export const db = new Low<DB>(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<Conversation, string> & {
fetchMessages: (conversationId: string) => Promise<Array<CommittedMessage>>;
} = {
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<Conversation>) => {
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<Conversation>) => {
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<CommittedMessage>;
},
};
const factTriggers: Entity<FactTrigger, string> & {
findByFactId: (factId: string) => Promise<Array<FactTrigger>>;
} = {
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<FactTrigger>) => {
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<FactTrigger>) => {
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<Fact, string> & {
findByConversationId: (conversationId: string) => Promise<Array<Fact>>;
} = {
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<Fact>) => {
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<Fact>) => {
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<Fact>;
},
};
const messages: Entity<CommittedMessage, string> & {
findByConversationId: (
conversationId: string
) => Promise<Array<CommittedMessage>>;
} = {
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<CommittedMessage>) => {
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<CommittedMessage>) => {
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,
};