transition to postgresql for persistence layer
This commit is contained in:
+33
-2
@@ -1,11 +1,42 @@
|
|||||||
const { makeKyselyHook } = require("kanel-kysely");
|
const { makeKyselyHook } = require("kanel-kysely");
|
||||||
|
const { resolveType, escapeIdentifier } = require("kanel");
|
||||||
|
const { recase } = require("@kristiandupont/recase");
|
||||||
|
const toPascalCase = recase("snake", "pascal");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
connection: "postgres://neondb_owner:npg_sOVmj8vWq2zG@ep-withered-king-adiz9gpi-pooler.c-2.us-east-1.aws.neon.tech:5432/neondb?sslmode=require&channel_binding=true",
|
connection:
|
||||||
|
"postgres://neondb_owner:npg_sOVmj8vWq2zG@ep-withered-king-adiz9gpi-pooler.c-2.us-east-1.aws.neon.tech:5432/neondb?sslmode=require&channel_binding=true",
|
||||||
enumStyle: "type",
|
enumStyle: "type",
|
||||||
outputPath: "./database/generated",
|
outputPath: "./database/generated",
|
||||||
preRenderHooks: [makeKyselyHook()],
|
preRenderHooks: [makeKyselyHook()],
|
||||||
|
generateIdentifierType: (column, details, config) => {
|
||||||
|
const name = escapeIdentifier(
|
||||||
|
toPascalCase(details.name) + toPascalCase(column.name)
|
||||||
|
);
|
||||||
|
const innerType = resolveType(column, details, {
|
||||||
|
...config,
|
||||||
|
// Explicitly disable identifier resolution so we get the actual inner type here
|
||||||
|
generateIdentifierType: undefined,
|
||||||
|
});
|
||||||
|
const imports = [];
|
||||||
|
|
||||||
|
let type = innerType;
|
||||||
|
if (typeof innerType === "object") {
|
||||||
|
// Handle non-primitives
|
||||||
|
type = innerType.name;
|
||||||
|
imports.push(...innerType.typeImports);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
declarationType: "typeDeclaration",
|
||||||
|
name,
|
||||||
|
exportAs: "named",
|
||||||
|
typeDefinition: [`${type}`],
|
||||||
|
typeImports: imports,
|
||||||
|
comment: [`Identifier type for ${details.schemaName}.${details.name}`],
|
||||||
|
};
|
||||||
|
},
|
||||||
customTypeMap: {
|
customTypeMap: {
|
||||||
"pg_catalog.timestamp": "string",
|
"pg_catalog.timestamp": "string",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
+58
-6
@@ -1,9 +1,61 @@
|
|||||||
export interface Entity<T, ID> {
|
import type { CommittedMessage } from "../types";
|
||||||
|
|
||||||
|
export type Conversation = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
userId: string;
|
||||||
|
createdAt?: 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 | null;
|
||||||
|
scopeConversationId: string;
|
||||||
|
createdAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Entity<T> {
|
||||||
construct: (data: T) => T;
|
construct: (data: T) => T;
|
||||||
create: (data: T) => Promise<T>;
|
create: (data: Omit<T, "id">) => Promise<T>;
|
||||||
createMany: (data: T[]) => Promise<T[]>;
|
createMany: (data: Omit<T, "id">[]) => Promise<T[]>;
|
||||||
findAll: () => Promise<T[]>;
|
findAll: () => Promise<T[]>;
|
||||||
findById: (id: ID) => Promise<T | undefined>;
|
findById: (id: string) => Promise<T | undefined>;
|
||||||
update: (id: ID, data: Partial<T>) => Promise<void>;
|
update: (id: string, data: Partial<T>) => Promise<void>;
|
||||||
delete: (id: ID) => Promise<void>;
|
delete: (id: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConversationEntity extends Entity<Conversation> {
|
||||||
|
fetchMessages: (conversationId: string) => Promise<Array<CommittedMessage>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FactEntity extends Entity<Fact> {
|
||||||
|
findByConversationId: (conversationId: string) => Promise<Array<Fact>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageEntity extends Entity<CommittedMessage> {
|
||||||
|
findByConversationId: (
|
||||||
|
conversationId: string
|
||||||
|
) => Promise<Array<CommittedMessage>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FactTriggerEntity = Entity<FactTrigger> & {
|
||||||
|
findByFactId: (factId: string) => Promise<Array<FactTrigger>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ApplicationDatabase {
|
||||||
|
conversations: ConversationEntity;
|
||||||
|
factTriggers: FactTriggerEntity;
|
||||||
|
facts: FactEntity;
|
||||||
|
messages: MessageEntity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import type { UsersId } from './Users';
|
|||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.conversations */
|
/** Identifier type for public.conversations */
|
||||||
export type ConversationsId = number & { __brand: 'public.conversations' };
|
export type ConversationsId = string;
|
||||||
|
|
||||||
/** Represents the table public.conversations */
|
/** Represents the table public.conversations */
|
||||||
export default interface ConversationsTable {
|
export default interface ConversationsTable {
|
||||||
id: ColumnType<ConversationsId, never, never>;
|
id: ColumnType<ConversationsId, ConversationsId | undefined, ConversationsId>;
|
||||||
|
|
||||||
title: ColumnType<string | null, string | null, string | null>;
|
title: ColumnType<string, string, string>;
|
||||||
|
|
||||||
createdAt: ColumnType<string | null, string | null, string | null>;
|
createdAt: ColumnType<string, string | undefined, string>;
|
||||||
|
|
||||||
userId: ColumnType<UsersId | null, UsersId | null, UsersId | null>;
|
userId: ColumnType<UsersId, UsersId, UsersId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Conversations = Selectable<ConversationsTable>;
|
export type Conversations = Selectable<ConversationsTable>;
|
||||||
|
|||||||
@@ -2,27 +2,26 @@
|
|||||||
// This file is automatically generated by Kanel. Do not modify manually.
|
// This file is automatically generated by Kanel. Do not modify manually.
|
||||||
|
|
||||||
import type { FactsId } from './Facts';
|
import type { FactsId } from './Facts';
|
||||||
import type { ConversationsId } from './Conversations';
|
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.fact_triggers */
|
/** Identifier type for public.fact_triggers */
|
||||||
export type FactTriggersId = number & { __brand: 'public.fact_triggers' };
|
export type FactTriggersId = string;
|
||||||
|
|
||||||
/** Represents the table public.fact_triggers */
|
/** Represents the table public.fact_triggers */
|
||||||
export default interface FactTriggersTable {
|
export default interface FactTriggersTable {
|
||||||
id: ColumnType<FactTriggersId, never, never>;
|
id: ColumnType<FactTriggersId, FactTriggersId | undefined, FactTriggersId>;
|
||||||
|
|
||||||
sourceFactId: ColumnType<FactsId | null, FactsId | null, FactsId | null>;
|
sourceFactId: ColumnType<FactsId, FactsId, FactsId>;
|
||||||
|
|
||||||
content: ColumnType<string | null, string | null, string | null>;
|
content: ColumnType<string, string, string>;
|
||||||
|
|
||||||
priorityMultiplier: ColumnType<number | null, number | null, number | null>;
|
priorityMultiplier: ColumnType<number, number | undefined, number>;
|
||||||
|
|
||||||
priorityMultiplierReason: ColumnType<string | null, string | null, string | null>;
|
priorityMultiplierReason: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
scopeConversationId: ColumnType<ConversationsId | null, ConversationsId | null, ConversationsId | null>;
|
scopeConversationId: ColumnType<string, string, string>;
|
||||||
|
|
||||||
createdAt: ColumnType<string | null, string | null, string | null>;
|
createdAt: ColumnType<string, string | undefined, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FactTriggers = Selectable<FactTriggersTable>;
|
export type FactTriggers = Selectable<FactTriggersTable>;
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ import type { MessagesId } from './Messages';
|
|||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.facts */
|
/** Identifier type for public.facts */
|
||||||
export type FactsId = number & { __brand: 'public.facts' };
|
export type FactsId = string;
|
||||||
|
|
||||||
/** Represents the table public.facts */
|
/** Represents the table public.facts */
|
||||||
export default interface FactsTable {
|
export default interface FactsTable {
|
||||||
id: ColumnType<FactsId, never, never>;
|
id: ColumnType<FactsId, FactsId | undefined, FactsId>;
|
||||||
|
|
||||||
userId: ColumnType<UsersId | null, UsersId | null, UsersId | null>;
|
userId: ColumnType<UsersId, UsersId, UsersId>;
|
||||||
|
|
||||||
sourceMessageId: ColumnType<MessagesId | null, MessagesId | null, MessagesId | null>;
|
sourceMessageId: ColumnType<MessagesId, MessagesId, MessagesId>;
|
||||||
|
|
||||||
content: ColumnType<string | null, string | null, string | null>;
|
content: ColumnType<string, string, string>;
|
||||||
|
|
||||||
createdAt: ColumnType<string | null, string | null, string | null>;
|
createdAt: ColumnType<string, string | undefined, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Facts = Selectable<FactsTable>;
|
export type Facts = Selectable<FactsTable>;
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import type { default as Role } from './Role';
|
|||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.messages */
|
/** Identifier type for public.messages */
|
||||||
export type MessagesId = number & { __brand: 'public.messages' };
|
export type MessagesId = string;
|
||||||
|
|
||||||
/** Represents the table public.messages */
|
/** Represents the table public.messages */
|
||||||
export default interface MessagesTable {
|
export default interface MessagesTable {
|
||||||
id: ColumnType<MessagesId, never, never>;
|
id: ColumnType<MessagesId, MessagesId | undefined, MessagesId>;
|
||||||
|
|
||||||
conversationId: ColumnType<ConversationsId | null, ConversationsId | null, ConversationsId | null>;
|
conversationId: ColumnType<ConversationsId | null, ConversationsId | null, ConversationsId | null>;
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ export default interface MessagesTable {
|
|||||||
|
|
||||||
runningSummary: ColumnType<string | null, string | null, string | null>;
|
runningSummary: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
created_at: ColumnType<string | null, string | null, string | null>;
|
createdAt: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
role: ColumnType<Role | null, Role | null, Role | null>;
|
role: ColumnType<Role, Role, Role>;
|
||||||
|
|
||||||
parts: ColumnType<unknown | null, unknown | null, unknown | null>;
|
parts: ColumnType<unknown | null, unknown | null, unknown | null>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import type { MessagesId } from './Messages';
|
|||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.tools */
|
/** Identifier type for public.tools */
|
||||||
export type ToolsId = number & { __brand: 'public.tools' };
|
export type ToolsId = string;
|
||||||
|
|
||||||
/** Represents the table public.tools */
|
/** Represents the table public.tools */
|
||||||
export default interface ToolsTable {
|
export default interface ToolsTable {
|
||||||
id: ColumnType<ToolsId, never, never>;
|
id: ColumnType<ToolsId, ToolsId | undefined, ToolsId>;
|
||||||
|
|
||||||
userId: ColumnType<UsersId | null, UsersId | null, UsersId | null>;
|
userId: ColumnType<UsersId | null, UsersId | null, UsersId | null>;
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.users */
|
/** Identifier type for public.users */
|
||||||
export type UsersId = number & { __brand: 'public.users' };
|
export type UsersId = string;
|
||||||
|
|
||||||
/** Represents the table public.users */
|
/** Represents the table public.users */
|
||||||
export default interface UsersTable {
|
export default interface UsersTable {
|
||||||
id: ColumnType<UsersId, never, never>;
|
id: ColumnType<UsersId, UsersId | undefined, UsersId>;
|
||||||
|
|
||||||
username: ColumnType<string | null, string | null, string | null>;
|
username: ColumnType<string | null, string | null, string | null>;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// export { db } from "./lowdb";
|
||||||
|
export { db } from "./postgres";
|
||||||
+103
-117
@@ -1,33 +1,17 @@
|
|||||||
import { Low } from "lowdb";
|
import { Low } from "lowdb";
|
||||||
import { JSONFile } from "lowdb/node";
|
import { JSONFile } from "lowdb/node";
|
||||||
import type { CommittedMessage } from "../types";
|
import type { CommittedMessage } from "../types";
|
||||||
import type { Entity } from "./common";
|
import type {
|
||||||
|
Conversation,
|
||||||
|
ConversationEntity,
|
||||||
|
Fact,
|
||||||
|
FactEntity,
|
||||||
|
FactTrigger,
|
||||||
|
FactTriggerEntity,
|
||||||
|
MessageEntity,
|
||||||
|
} from "./common";
|
||||||
import { nanoid } from "nanoid";
|
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 = {
|
type DB = {
|
||||||
conversations: Array<Conversation>;
|
conversations: Array<Conversation>;
|
||||||
messages: Array</*{
|
messages: Array</*{
|
||||||
@@ -43,217 +27,219 @@ type DB = {
|
|||||||
factTriggers: Array<FactTrigger>;
|
factTriggers: Array<FactTrigger>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const db = new Low<DB>(new JSONFile("db.json"), {
|
export const dbClient = new Low<DB>(new JSONFile("db.json"), {
|
||||||
conversations: [],
|
conversations: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
facts: [],
|
facts: [],
|
||||||
factTriggers: [],
|
factTriggers: [],
|
||||||
});
|
});
|
||||||
/** Initialize the database. Sets `db.data` to the default state if the file doesn't exist. */
|
/** Initialize the database. Sets `db.data` to the default state if the file doesn't exist. */
|
||||||
await db.read();
|
await dbClient.read();
|
||||||
/** Write the database to the file, in case it didn't exist before. */
|
/** Write the database to the file, in case it didn't exist before. */
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
|
|
||||||
const conversations: Entity<Conversation, string> & {
|
const conversations: ConversationEntity = {
|
||||||
fetchMessages: (conversationId: string) => Promise<Array<CommittedMessage>>;
|
|
||||||
} = {
|
|
||||||
construct: (conversation: Conversation) => conversation,
|
construct: (conversation: Conversation) => conversation,
|
||||||
create: async (conversation: Conversation) => {
|
create: async (_conversation) => {
|
||||||
conversation.id = conversation.id ?? nanoid();
|
const conversation = { ..._conversation, id: nanoid() };
|
||||||
await db.data.conversations.push(conversation);
|
await dbClient.data.conversations.push(conversation);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
return conversation;
|
return conversation;
|
||||||
},
|
},
|
||||||
createMany: async (conversations: Array<Conversation>) => {
|
createMany: async (_conversations) => {
|
||||||
await db.data.conversations.push(...conversations);
|
const conversations = _conversations.map((c) => ({ ...c, id: nanoid() }));
|
||||||
await db.write();
|
await dbClient.data.conversations.push(...conversations);
|
||||||
|
await dbClient.write();
|
||||||
return conversations;
|
return conversations;
|
||||||
},
|
},
|
||||||
findAll: async () => {
|
findAll: async () => {
|
||||||
return db.data.conversations;
|
return dbClient.data.conversations;
|
||||||
},
|
},
|
||||||
findById: async (id) => {
|
findById: async (id) => {
|
||||||
return db.data.conversations.find((c) => c.id === id);
|
return dbClient.data.conversations.find((c) => c.id === id);
|
||||||
},
|
},
|
||||||
update: async (id, data: Partial<Conversation>) => {
|
update: async (id, data: Partial<Conversation>) => {
|
||||||
const conversationIndex = db.data.conversations.findIndex(
|
const conversationIndex = dbClient.data.conversations.findIndex(
|
||||||
(c) => c.id === id
|
(c) => c.id === id
|
||||||
);
|
);
|
||||||
if (conversationIndex === -1) throw new Error("Conversation not found");
|
if (conversationIndex === -1) throw new Error("Conversation not found");
|
||||||
db.data.conversations[conversationIndex] = {
|
dbClient.data.conversations[conversationIndex] = {
|
||||||
...db.data.conversations[conversationIndex],
|
...dbClient.data.conversations[conversationIndex],
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
db.data.conversations.splice(
|
dbClient.data.conversations.splice(
|
||||||
db.data.conversations.findIndex((c) => c.id === id),
|
dbClient.data.conversations.findIndex((c) => c.id === id),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
const deletedMessageIds = db.data.messages
|
const deletedMessageIds = dbClient.data.messages
|
||||||
.filter((m) => m.conversationId === id)
|
.filter((m) => m.conversationId === id)
|
||||||
.map((m) => m.id);
|
.map((m) => m.id);
|
||||||
db.data.messages = db.data.messages.filter((m) => m.conversationId !== id);
|
dbClient.data.messages = dbClient.data.messages.filter(
|
||||||
const deletedFactIds = db.data.facts
|
(m) => m.conversationId !== id
|
||||||
|
);
|
||||||
|
const deletedFactIds = dbClient.data.facts
|
||||||
.filter((fact) => deletedMessageIds.includes(fact.sourceMessageId))
|
.filter((fact) => deletedMessageIds.includes(fact.sourceMessageId))
|
||||||
.map((fact) => fact.id);
|
.map((fact) => fact.id);
|
||||||
db.data.facts = db.data.facts.filter(
|
dbClient.data.facts = dbClient.data.facts.filter(
|
||||||
(fact) => !deletedFactIds.includes(fact.id)
|
(fact) => !deletedFactIds.includes(fact.id)
|
||||||
);
|
);
|
||||||
db.data.factTriggers = db.data.factTriggers.filter(
|
dbClient.data.factTriggers = dbClient.data.factTriggers.filter(
|
||||||
(factTrigger) => !deletedFactIds.includes(factTrigger.sourceFactId)
|
(factTrigger) => !deletedFactIds.includes(factTrigger.sourceFactId)
|
||||||
);
|
);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
fetchMessages: async (conversationId) => {
|
fetchMessages: async (conversationId) => {
|
||||||
const rows = await db.data.messages.filter(
|
const rows = await dbClient.data.messages.filter(
|
||||||
(m) => m.conversationId === conversationId
|
(m) => m.conversationId === conversationId
|
||||||
);
|
);
|
||||||
return rows as Array<CommittedMessage>;
|
return rows as Array<CommittedMessage>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const factTriggers: Entity<FactTrigger, string> & {
|
const factTriggers: FactTriggerEntity = {
|
||||||
findByFactId: (factId: string) => Promise<Array<FactTrigger>>;
|
|
||||||
} = {
|
|
||||||
construct: (factTrigger: FactTrigger) => factTrigger,
|
construct: (factTrigger: FactTrigger) => factTrigger,
|
||||||
create: async (factTrigger: FactTrigger) => {
|
create: async (_factTrigger) => {
|
||||||
factTrigger.id = factTrigger.id ?? nanoid();
|
const factTrigger = { ..._factTrigger, id: nanoid() };
|
||||||
await db.data.factTriggers.push(factTrigger);
|
await dbClient.data.factTriggers.push(factTrigger);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
return factTrigger;
|
return factTrigger;
|
||||||
},
|
},
|
||||||
createMany: async (factTriggers: Array<FactTrigger>) => {
|
createMany: async (_factTriggers) => {
|
||||||
await db.data.factTriggers.push(...factTriggers);
|
const factTriggers = _factTriggers.map((f) => ({ ...f, id: nanoid() }));
|
||||||
await db.write();
|
await dbClient.data.factTriggers.push(...factTriggers);
|
||||||
|
await dbClient.write();
|
||||||
return factTriggers;
|
return factTriggers;
|
||||||
},
|
},
|
||||||
findAll: async () => {
|
findAll: async () => {
|
||||||
return db.data.factTriggers;
|
return dbClient.data.factTriggers;
|
||||||
},
|
},
|
||||||
findById: async (id) => {
|
findById: async (id) => {
|
||||||
return db.data.factTriggers.find((factTrigger) => factTrigger.id === id);
|
return dbClient.data.factTriggers.find(
|
||||||
|
(factTrigger) => factTrigger.id === id
|
||||||
|
);
|
||||||
},
|
},
|
||||||
update: async (id, data: Partial<FactTrigger>) => {
|
update: async (id, data: Partial<FactTrigger>) => {
|
||||||
const factTriggerIndex = db.data.factTriggers.findIndex(
|
const factTriggerIndex = dbClient.data.factTriggers.findIndex(
|
||||||
(factTrigger) => factTrigger.id === id
|
(factTrigger) => factTrigger.id === id
|
||||||
);
|
);
|
||||||
if (factTriggerIndex === -1) throw new Error("Fact trigger not found");
|
if (factTriggerIndex === -1) throw new Error("Fact trigger not found");
|
||||||
db.data.factTriggers[factTriggerIndex] = {
|
dbClient.data.factTriggers[factTriggerIndex] = {
|
||||||
...db.data.factTriggers[factTriggerIndex],
|
...dbClient.data.factTriggers[factTriggerIndex],
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
const deletedFactTriggerIndex = db.data.factTriggers.findIndex(
|
const deletedFactTriggerIndex = dbClient.data.factTriggers.findIndex(
|
||||||
(factTrigger) => factTrigger.id === id
|
(factTrigger) => factTrigger.id === id
|
||||||
);
|
);
|
||||||
if (deletedFactTriggerIndex === -1)
|
if (deletedFactTriggerIndex === -1)
|
||||||
throw new Error("Fact trigger not found");
|
throw new Error("Fact trigger not found");
|
||||||
db.data.factTriggers.splice(deletedFactTriggerIndex, 1);
|
dbClient.data.factTriggers.splice(deletedFactTriggerIndex, 1);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
findByFactId: async (factId: string) => {
|
findByFactId: async (factId: string) => {
|
||||||
return db.data.factTriggers.filter(
|
return dbClient.data.factTriggers.filter(
|
||||||
(factTrigger) => factTrigger.sourceFactId === factId
|
(factTrigger) => factTrigger.sourceFactId === factId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const facts: Entity<Fact, string> & {
|
const facts: FactEntity = {
|
||||||
findByConversationId: (conversationId: string) => Promise<Array<Fact>>;
|
|
||||||
} = {
|
|
||||||
construct: (fact: Fact) => fact,
|
construct: (fact: Fact) => fact,
|
||||||
create: async (fact: Fact) => {
|
create: async (_fact) => {
|
||||||
fact.id = fact.id ?? nanoid();
|
const fact = { ..._fact, id: nanoid() };
|
||||||
await db.data.facts.push(fact);
|
await dbClient.data.facts.push(fact);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
return fact;
|
return fact;
|
||||||
},
|
},
|
||||||
createMany: async (facts: Array<Fact>) => {
|
createMany: async (_facts) => {
|
||||||
await db.data.facts.push(...facts);
|
const facts = _facts.map((f) => ({ ...f, id: nanoid() }));
|
||||||
await db.write();
|
await dbClient.data.facts.push(...facts);
|
||||||
|
await dbClient.write();
|
||||||
return facts;
|
return facts;
|
||||||
},
|
},
|
||||||
findAll: async () => {
|
findAll: async () => {
|
||||||
return db.data.facts;
|
return dbClient.data.facts;
|
||||||
},
|
},
|
||||||
findById: async (id) => {
|
findById: async (id) => {
|
||||||
return db.data.facts.find((fact) => fact.id === id);
|
return dbClient.data.facts.find((fact) => fact.id === id);
|
||||||
},
|
},
|
||||||
update: async (id, data: Partial<Fact>) => {
|
update: async (id, data: Partial<Fact>) => {
|
||||||
const factIndex = db.data.facts.findIndex((fact) => fact.id === id);
|
const factIndex = dbClient.data.facts.findIndex((fact) => fact.id === id);
|
||||||
if (factIndex === -1) throw new Error("Fact not found");
|
if (factIndex === -1) throw new Error("Fact not found");
|
||||||
db.data.facts[factIndex] = {
|
dbClient.data.facts[factIndex] = {
|
||||||
...db.data.facts[factIndex],
|
...dbClient.data.facts[factIndex],
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
const deletedFactId = db.data.facts.findIndex((fact) => fact.id === id);
|
const deletedFactId = dbClient.data.facts.findIndex(
|
||||||
|
(fact) => fact.id === id
|
||||||
|
);
|
||||||
if (deletedFactId === -1) throw new Error("Fact not found");
|
if (deletedFactId === -1) throw new Error("Fact not found");
|
||||||
db.data.facts.splice(deletedFactId, 1);
|
dbClient.data.facts.splice(deletedFactId, 1);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
findByConversationId: async (conversationId) => {
|
findByConversationId: async (conversationId) => {
|
||||||
const conversationMessageIds = db.data.messages
|
const conversationMessageIds = dbClient.data.messages
|
||||||
.filter((m) => m.conversationId === conversationId)
|
.filter((m) => m.conversationId === conversationId)
|
||||||
.map((m) => m.id);
|
.map((m) => m.id);
|
||||||
const rows = await db.data.facts.filter((f) =>
|
const rows = await dbClient.data.facts.filter((f) =>
|
||||||
conversationMessageIds.includes(f.sourceMessageId)
|
conversationMessageIds.includes(f.sourceMessageId)
|
||||||
);
|
);
|
||||||
return rows as Array<Fact>;
|
return rows as Array<Fact>;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages: Entity<CommittedMessage, string> & {
|
const messages: MessageEntity = {
|
||||||
findByConversationId: (
|
|
||||||
conversationId: string
|
|
||||||
) => Promise<Array<CommittedMessage>>;
|
|
||||||
} = {
|
|
||||||
construct: (message: CommittedMessage) => message,
|
construct: (message: CommittedMessage) => message,
|
||||||
create: async (message: CommittedMessage) => {
|
create: async (_message) => {
|
||||||
message.id = message.id ?? nanoid();
|
const message = { ..._message, id: nanoid() };
|
||||||
await db.data.messages.push(message);
|
await dbClient.data.messages.push(message);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
createMany: async (messages: Array<CommittedMessage>) => {
|
createMany: async (_messages) => {
|
||||||
await db.data.messages.push(...messages);
|
const messages = _messages.map((m) => ({ ...m, id: nanoid() }));
|
||||||
await db.write();
|
await dbClient.data.messages.push(...messages);
|
||||||
|
await dbClient.write();
|
||||||
return messages;
|
return messages;
|
||||||
},
|
},
|
||||||
findAll: async () => {
|
findAll: async () => {
|
||||||
return db.data.messages;
|
return dbClient.data.messages;
|
||||||
},
|
},
|
||||||
findById: async (id) => {
|
findById: async (id) => {
|
||||||
return db.data.messages.find((m) => m.id === id);
|
return dbClient.data.messages.find((m) => m.id === id);
|
||||||
},
|
},
|
||||||
update: async (id, data: Partial<CommittedMessage>) => {
|
update: async (id, data: Partial<CommittedMessage>) => {
|
||||||
const messageIndex = db.data.messages.findIndex((m) => m.id === id);
|
const messageIndex = dbClient.data.messages.findIndex((m) => m.id === id);
|
||||||
if (messageIndex === -1) throw new Error("Message not found");
|
if (messageIndex === -1) throw new Error("Message not found");
|
||||||
db.data.messages[messageIndex] = {
|
dbClient.data.messages[messageIndex] = {
|
||||||
...db.data.messages[messageIndex],
|
...dbClient.data.messages[messageIndex],
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
delete: async (id) => {
|
delete: async (id) => {
|
||||||
db.data.messages.splice(
|
dbClient.data.messages.splice(
|
||||||
db.data.messages.findIndex((m) => m.id === id),
|
dbClient.data.messages.findIndex((m) => m.id === id),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
await db.write();
|
await dbClient.write();
|
||||||
},
|
},
|
||||||
findByConversationId: async (conversationId) => {
|
findByConversationId: async (conversationId) => {
|
||||||
return db.data.messages.filter((m) => m.conversationId === conversationId);
|
return dbClient.data.messages.filter(
|
||||||
|
(m) => m.conversationId === conversationId
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const _db = {
|
export const db = {
|
||||||
conversations,
|
conversations,
|
||||||
factTriggers,
|
factTriggers,
|
||||||
facts,
|
facts,
|
||||||
|
|||||||
+229
-1
@@ -1,6 +1,14 @@
|
|||||||
import { Pool } from "pg";
|
import { Pool } from "pg";
|
||||||
import { Kysely, PostgresDialect } from "kysely";
|
import { Kysely, PostgresDialect } from "kysely";
|
||||||
import type Database from "./generated/Database";
|
import type Database from "./generated/Database";
|
||||||
|
import type {
|
||||||
|
ConversationEntity,
|
||||||
|
FactEntity,
|
||||||
|
FactTriggerEntity,
|
||||||
|
MessageEntity,
|
||||||
|
} from "./common.ts";
|
||||||
|
import type { Messages } from "./generated/public/Messages";
|
||||||
|
import type { CommittedMessage } from "../types";
|
||||||
|
|
||||||
export const pool = new Pool({
|
export const pool = new Pool({
|
||||||
connectionString:
|
connectionString:
|
||||||
@@ -16,6 +24,226 @@ const dialect = new PostgresDialect({
|
|||||||
// knows your database structure.
|
// knows your database structure.
|
||||||
// Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
|
// Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
|
||||||
// to communicate with your database.
|
// to communicate with your database.
|
||||||
export const db = new Kysely<Database>({
|
export const dbClient = new Kysely<Database>({
|
||||||
dialect,
|
dialect,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const conversations: ConversationEntity = {
|
||||||
|
construct: (conversation) => conversation,
|
||||||
|
create: async (conversation) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("conversations")
|
||||||
|
.values(conversation)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows[0];
|
||||||
|
},
|
||||||
|
createMany: async (conversations) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("conversations")
|
||||||
|
.values(conversations)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows;
|
||||||
|
},
|
||||||
|
findAll: async () => {
|
||||||
|
const rows = await dbClient
|
||||||
|
.selectFrom("conversations")
|
||||||
|
.selectAll()
|
||||||
|
.execute();
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
findById: async (id) => {
|
||||||
|
const row = await dbClient
|
||||||
|
.selectFrom("conversations")
|
||||||
|
.selectAll()
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
return row[0];
|
||||||
|
},
|
||||||
|
update: async (id, data) => {
|
||||||
|
await dbClient
|
||||||
|
.updateTable("conversations")
|
||||||
|
.set(data)
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
},
|
||||||
|
delete: async (id) => {
|
||||||
|
await dbClient.deleteFrom("conversations").where("id", "=", id).execute();
|
||||||
|
},
|
||||||
|
fetchMessages: async (conversationId) => {
|
||||||
|
const rows = await dbClient
|
||||||
|
.selectFrom("messages")
|
||||||
|
.selectAll()
|
||||||
|
.where("conversationId", "=", conversationId)
|
||||||
|
.execute();
|
||||||
|
return rows as Array<CommittedMessage>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const facts: FactEntity = {
|
||||||
|
construct: (fact) => fact,
|
||||||
|
create: async (fact) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("facts")
|
||||||
|
.values(fact)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows[0];
|
||||||
|
},
|
||||||
|
createMany: async (facts) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("facts")
|
||||||
|
.values(facts)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows;
|
||||||
|
},
|
||||||
|
findAll: async () => {
|
||||||
|
const rows = await dbClient.selectFrom("facts").selectAll().execute();
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
findById: async (id) => {
|
||||||
|
const row = await dbClient
|
||||||
|
.selectFrom("facts")
|
||||||
|
.selectAll()
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
return row[0];
|
||||||
|
},
|
||||||
|
update: async (id, data) => {
|
||||||
|
await dbClient
|
||||||
|
.updateTable("facts")
|
||||||
|
.set(data)
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
},
|
||||||
|
delete: async (id) => {
|
||||||
|
await dbClient.deleteFrom("facts").where("id", "=", id).execute();
|
||||||
|
},
|
||||||
|
findByConversationId: async (conversationId) => {
|
||||||
|
const rows = await dbClient
|
||||||
|
.selectFrom("facts")
|
||||||
|
.innerJoin("messages", "messages.id", "facts.sourceMessageId")
|
||||||
|
.selectAll("facts")
|
||||||
|
.where("conversationId", "=", conversationId)
|
||||||
|
.execute();
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const factTriggers: FactTriggerEntity = {
|
||||||
|
construct: (factTrigger) => factTrigger,
|
||||||
|
create: async (factTrigger) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("fact_triggers")
|
||||||
|
.values(factTrigger)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows[0];
|
||||||
|
},
|
||||||
|
createMany: async (factTriggers) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("fact_triggers")
|
||||||
|
.values(factTriggers)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows;
|
||||||
|
},
|
||||||
|
findAll: async () => {
|
||||||
|
const rows = await dbClient
|
||||||
|
.selectFrom("fact_triggers")
|
||||||
|
.selectAll()
|
||||||
|
.execute();
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
findById: async (id) => {
|
||||||
|
const row = await dbClient
|
||||||
|
.selectFrom("fact_triggers")
|
||||||
|
.selectAll()
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
return row[0];
|
||||||
|
},
|
||||||
|
update: async (id, data) => {
|
||||||
|
await dbClient
|
||||||
|
.updateTable("fact_triggers")
|
||||||
|
.set(data)
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
},
|
||||||
|
delete: async (id) => {
|
||||||
|
await dbClient.deleteFrom("fact_triggers").where("id", "=", id).execute();
|
||||||
|
},
|
||||||
|
findByFactId: async (factId) => {
|
||||||
|
const rows = await dbClient
|
||||||
|
.selectFrom("fact_triggers")
|
||||||
|
.innerJoin("facts", "facts.id", "fact_triggers.sourceFactId")
|
||||||
|
.selectAll("fact_triggers")
|
||||||
|
.where("sourceFactId", "=", factId)
|
||||||
|
.execute();
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages: MessageEntity = {
|
||||||
|
construct: (message) => message,
|
||||||
|
create: async (message) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("messages")
|
||||||
|
.values({ ...message, parts: JSON.stringify(message.parts) })
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows[0] as CommittedMessage;
|
||||||
|
},
|
||||||
|
createMany: async (messages) => {
|
||||||
|
const insertedRows = await dbClient
|
||||||
|
.insertInto("messages")
|
||||||
|
.values(
|
||||||
|
messages.map((message) => ({
|
||||||
|
...message,
|
||||||
|
parts: JSON.stringify(message.parts),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.returningAll()
|
||||||
|
.execute();
|
||||||
|
return insertedRows as Array<CommittedMessage>;
|
||||||
|
},
|
||||||
|
findAll: async () => {
|
||||||
|
const rows = await dbClient.selectFrom("messages").selectAll().execute();
|
||||||
|
return rows as Array<CommittedMessage>;
|
||||||
|
},
|
||||||
|
findById: async (id) => {
|
||||||
|
const row = await dbClient
|
||||||
|
.selectFrom("messages")
|
||||||
|
.selectAll()
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
return row[0] as CommittedMessage;
|
||||||
|
},
|
||||||
|
update: async (id, data) => {
|
||||||
|
await dbClient
|
||||||
|
.updateTable("messages")
|
||||||
|
.set(data)
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
},
|
||||||
|
delete: async (id) => {
|
||||||
|
await dbClient.deleteFrom("messages").where("id", "=", id).execute();
|
||||||
|
},
|
||||||
|
findByConversationId: async (conversationId) => {
|
||||||
|
const rows = await dbClient
|
||||||
|
.selectFrom("messages")
|
||||||
|
.selectAll()
|
||||||
|
.where("conversationId", "=", conversationId)
|
||||||
|
.execute();
|
||||||
|
return rows as Array<CommittedMessage>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const db = {
|
||||||
|
conversations,
|
||||||
|
facts,
|
||||||
|
factTriggers,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@cloudflare/workers-types": "^4.20250620.0",
|
"@cloudflare/workers-types": "^4.20250620.0",
|
||||||
"@hono/vite-dev-server": "^0.19.1",
|
"@hono/vite-dev-server": "^0.19.1",
|
||||||
|
"@kristiandupont/recase": "^1.4.1",
|
||||||
"@types/node": "^20.19.0",
|
"@types/node": "^20.19.0",
|
||||||
"@types/pg": "^8.15.4",
|
"@types/pg": "^8.15.4",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
|
|||||||
@@ -3,29 +3,28 @@ import {
|
|||||||
publicProcedure,
|
publicProcedure,
|
||||||
createCallerFactory,
|
createCallerFactory,
|
||||||
} from "../../trpc/server";
|
} from "../../trpc/server";
|
||||||
import { _db } from "../../database/lowdb";
|
import { db } from "../../database/index";
|
||||||
|
|
||||||
export const conversations = router({
|
export const conversations = router({
|
||||||
fetchAll: publicProcedure.query(async () => {
|
fetchAll: publicProcedure.query(async () => {
|
||||||
return await _db.conversations.findAll();
|
return await db.conversations.findAll();
|
||||||
}),
|
}),
|
||||||
fetchOne: publicProcedure
|
fetchOne: publicProcedure
|
||||||
.input((x) => x as { id: string })
|
.input((x) => x as { id: string })
|
||||||
.query(async ({ input: { id } }) => {
|
.query(async ({ input: { id } }) => {
|
||||||
return await _db.conversations.findById(id);
|
return await db.conversations.findById(id);
|
||||||
}),
|
}),
|
||||||
start: publicProcedure.mutation(async () => {
|
start: publicProcedure.mutation(async () => {
|
||||||
const row = {
|
const row = {
|
||||||
id: "",
|
|
||||||
title: "New Conversation",
|
title: "New Conversation",
|
||||||
userId: "1",
|
userId: "019900bb-61b3-7333-b760-b27784dfe33b",
|
||||||
};
|
};
|
||||||
return await _db.conversations.create(row);
|
return await db.conversations.create(row);
|
||||||
}),
|
}),
|
||||||
deleteOne: publicProcedure
|
deleteOne: publicProcedure
|
||||||
.input((x) => x as { id: string })
|
.input((x) => x as { id: string })
|
||||||
.mutation(async ({ input: { id } }) => {
|
.mutation(async ({ input: { id } }) => {
|
||||||
await _db.conversations.delete(id);
|
await db.conversations.delete(id);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}),
|
}),
|
||||||
updateTitle: publicProcedure
|
updateTitle: publicProcedure
|
||||||
@@ -37,13 +36,13 @@ export const conversations = router({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { id, title } }) => {
|
.mutation(async ({ input: { id, title } }) => {
|
||||||
await _db.conversations.update(id, { title });
|
await db.conversations.update(id, { title });
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}),
|
}),
|
||||||
fetchMessages: publicProcedure
|
fetchMessages: publicProcedure
|
||||||
.input((x) => x as { conversationId: string })
|
.input((x) => x as { conversationId: string })
|
||||||
.query(async ({ input: { conversationId } }) => {
|
.query(async ({ input: { conversationId } }) => {
|
||||||
return await _db.conversations.fetchMessages(conversationId);
|
return await db.conversations.fetchMessages(conversationId);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import {
|
|||||||
publicProcedure,
|
publicProcedure,
|
||||||
createCallerFactory,
|
createCallerFactory,
|
||||||
} from "../../trpc/server.js";
|
} from "../../trpc/server.js";
|
||||||
import { _db, type Fact } from "../../database/lowdb.js";
|
import { db } from "../../database/index.js";
|
||||||
import type { DraftMessage } from "../../types.js";
|
import type { DraftMessage } from "../../types.js";
|
||||||
import { openrouter, MODEL_NAME } from "./provider.js";
|
import { openrouter, MODEL_NAME } from "./provider.js";
|
||||||
import { generateObject, generateText, jsonSchema } from "ai";
|
import { generateObject, generateText, jsonSchema } from "ai";
|
||||||
|
import type { Fact } from "../../database/common.js";
|
||||||
|
|
||||||
const factTriggersSystemPrompt = ({
|
const factTriggersSystemPrompt = ({
|
||||||
previousRunningSummary,
|
previousRunningSummary,
|
||||||
@@ -61,7 +62,7 @@ export const factTriggers = router({
|
|||||||
fetchByFactId: publicProcedure
|
fetchByFactId: publicProcedure
|
||||||
.input((x) => x as { factId: string })
|
.input((x) => x as { factId: string })
|
||||||
.query(async ({ input: { factId } }) => {
|
.query(async ({ input: { factId } }) => {
|
||||||
return _db.factTriggers.findByFactId(factId);
|
return db.factTriggers.findByFactId(factId);
|
||||||
}),
|
}),
|
||||||
deleteOne: publicProcedure
|
deleteOne: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -71,7 +72,7 @@ export const factTriggers = router({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { factTriggerId } }) => {
|
.mutation(async ({ input: { factTriggerId } }) => {
|
||||||
await _db.factTriggers.delete(factTriggerId);
|
await db.factTriggers.delete(factTriggerId);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}),
|
}),
|
||||||
update: publicProcedure
|
update: publicProcedure
|
||||||
@@ -83,7 +84,7 @@ export const factTriggers = router({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { factTriggerId, content } }) => {
|
.mutation(async ({ input: { factTriggerId, content } }) => {
|
||||||
_db.factTriggers.update(factTriggerId, { content });
|
db.factTriggers.update(factTriggerId, { content });
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}),
|
}),
|
||||||
generateFromFact: publicProcedure
|
generateFromFact: publicProcedure
|
||||||
|
|||||||
+4
-4
@@ -3,7 +3,7 @@ import {
|
|||||||
publicProcedure,
|
publicProcedure,
|
||||||
createCallerFactory,
|
createCallerFactory,
|
||||||
} from "../../trpc/server.js";
|
} from "../../trpc/server.js";
|
||||||
import { _db } from "../../database/lowdb.js";
|
import { db } from "../../database/index.js";
|
||||||
import type { DraftMessage } from "../../types.js";
|
import type { DraftMessage } from "../../types.js";
|
||||||
import { MODEL_NAME, openrouter } from "./provider.js";
|
import { MODEL_NAME, openrouter } from "./provider.js";
|
||||||
import { generateObject, generateText, jsonSchema } from "ai";
|
import { generateObject, generateText, jsonSchema } from "ai";
|
||||||
@@ -57,7 +57,7 @@ export const facts = router({
|
|||||||
fetchByConversationId: publicProcedure
|
fetchByConversationId: publicProcedure
|
||||||
.input((x) => x as { conversationId: string })
|
.input((x) => x as { conversationId: string })
|
||||||
.query(async ({ input: { conversationId } }) => {
|
.query(async ({ input: { conversationId } }) => {
|
||||||
return await _db.facts.findByConversationId(conversationId);
|
return await db.facts.findByConversationId(conversationId);
|
||||||
}),
|
}),
|
||||||
deleteOne: publicProcedure
|
deleteOne: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -67,7 +67,7 @@ export const facts = router({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { factId } }) => {
|
.mutation(async ({ input: { factId } }) => {
|
||||||
await _db.facts.delete(factId);
|
await db.facts.delete(factId);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}),
|
}),
|
||||||
update: publicProcedure
|
update: publicProcedure
|
||||||
@@ -79,7 +79,7 @@ export const facts = router({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { factId, content } }) => {
|
.mutation(async ({ input: { factId, content } }) => {
|
||||||
await _db.facts.update(factId, { content });
|
await db.facts.update(factId, { content });
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}),
|
}),
|
||||||
extractFromNewMessages: publicProcedure
|
extractFromNewMessages: publicProcedure
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
import { MODEL_NAME, openrouter } from "./provider.js";
|
import { MODEL_NAME, openrouter } from "./provider.js";
|
||||||
import { generateObject, generateText, jsonSchema } from "ai";
|
import { generateObject, generateText, jsonSchema } from "ai";
|
||||||
import type { DraftMessage } from "../../types.js";
|
import type { DraftMessage } from "../../types.js";
|
||||||
import { _db } from "../../database/lowdb";
|
import { db } from "../../database/index";
|
||||||
|
|
||||||
const runningSummarySystemPrompt = ({
|
const runningSummarySystemPrompt = ({
|
||||||
previousRunningSummary,
|
previousRunningSummary,
|
||||||
@@ -52,7 +52,7 @@ export const messages = router({
|
|||||||
fetchByConversationId: publicProcedure
|
fetchByConversationId: publicProcedure
|
||||||
.input((x) => x as { conversationId: string })
|
.input((x) => x as { conversationId: string })
|
||||||
.query(async ({ input: { conversationId } }) => {
|
.query(async ({ input: { conversationId } }) => {
|
||||||
return await _db.messages.findByConversationId(conversationId);
|
return await db.messages.findByConversationId(conversationId);
|
||||||
}),
|
}),
|
||||||
generateRunningSummary: publicProcedure
|
generateRunningSummary: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ export const openrouter = createOpenRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// export const MODEL_NAME = "mistralai/mistral-nemo";
|
// export const MODEL_NAME = "mistralai/mistral-nemo";
|
||||||
// export const MODEL_NAME = "openai/gpt-oss-20b";
|
export const MODEL_NAME = "openai/gpt-oss-20b";
|
||||||
export const MODEL_NAME = "openai/gpt-5-mini";
|
// export const MODEL_NAME = "openai/gpt-5-mini";
|
||||||
|
|||||||
+17
-25
@@ -14,8 +14,7 @@ import type {
|
|||||||
// ConsistencyLevelEnum,
|
// ConsistencyLevelEnum,
|
||||||
// type NumberArrayId,
|
// type NumberArrayId,
|
||||||
// } from "@zilliz/milvus2-sdk-node";
|
// } from "@zilliz/milvus2-sdk-node";
|
||||||
import { db, type FactTrigger, type Fact, _db } from "../../database/lowdb.js";
|
import { db } from "../../database/index.js";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { conversations } from "./conversations.js";
|
import { conversations } from "./conversations.js";
|
||||||
import { messages } from "./messages.js";
|
import { messages } from "./messages.js";
|
||||||
import { facts, createCaller as createCallerFacts } from "./facts.js";
|
import { facts, createCaller as createCallerFacts } from "./facts.js";
|
||||||
@@ -23,6 +22,7 @@ import { createCaller as createCallerMessages } from "./messages.js";
|
|||||||
import { createCaller as createCallerFactTriggers } from "./fact-triggers.js";
|
import { createCaller as createCallerFactTriggers } from "./fact-triggers.js";
|
||||||
import { factTriggers } from "./fact-triggers.js";
|
import { factTriggers } from "./fact-triggers.js";
|
||||||
import { MODEL_NAME, openrouter } from "./provider.js";
|
import { MODEL_NAME, openrouter } from "./provider.js";
|
||||||
|
import type { Fact, FactTrigger } from "../../database/common.js";
|
||||||
|
|
||||||
const factsCaller = createCallerFacts({});
|
const factsCaller = createCallerFacts({});
|
||||||
const messagesCaller = createCallerMessages({});
|
const messagesCaller = createCallerMessages({});
|
||||||
@@ -96,16 +96,14 @@ export const chat = router({
|
|||||||
previousRunningSummaryIndex + 1
|
previousRunningSummaryIndex + 1
|
||||||
);
|
);
|
||||||
/** Save the incoming message to the database. */
|
/** Save the incoming message to the database. */
|
||||||
const insertedUserMessage: CommittedMessage = {
|
const insertedUserMessage = await db.messages.create({
|
||||||
id: nanoid(),
|
|
||||||
conversationId,
|
conversationId,
|
||||||
// content: messages[messages.length - 1].content,
|
// content: messages[messages.length - 1].content,
|
||||||
// role: "user" as const,
|
// role: "user" as const,
|
||||||
...messages[messages.length - 1],
|
...messages[messages.length - 1],
|
||||||
index: messages.length - 1,
|
index: messages.length - 1,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
};
|
});
|
||||||
await _db.messages.create(insertedUserMessage);
|
|
||||||
|
|
||||||
/** Generate a new message from the model, but hold-off on adding it to
|
/** Generate a new message from the model, but hold-off on adding it to
|
||||||
* the database until we produce the associated running-summary, below.
|
* the database until we produce the associated running-summary, below.
|
||||||
@@ -154,15 +152,13 @@ export const chat = router({
|
|||||||
messagesSincePreviousRunningSummary: [],
|
messagesSincePreviousRunningSummary: [],
|
||||||
newMessages: messagesSincePreviousRunningSummary,
|
newMessages: messagesSincePreviousRunningSummary,
|
||||||
});
|
});
|
||||||
const insertedFactsFromUserMessage: Array<Fact> =
|
const insertedFactsFromUserMessage = await db.facts.createMany(
|
||||||
factsFromUserMessageResponse.object.facts.map((fact) => ({
|
factsFromUserMessageResponse.object.facts.map((fact) => ({
|
||||||
id: nanoid(),
|
userId: "019900bb-61b3-7333-b760-b27784dfe33b",
|
||||||
userId: "1",
|
|
||||||
sourceMessageId: insertedUserMessage.id,
|
sourceMessageId: insertedUserMessage.id,
|
||||||
content: fact,
|
content: fact,
|
||||||
createdAt: new Date().toISOString(),
|
}))
|
||||||
}));
|
);
|
||||||
_db.facts.createMany(insertedFactsFromUserMessage);
|
|
||||||
|
|
||||||
/** Produce a running summary of the conversation, and save that along
|
/** Produce a running summary of the conversation, and save that along
|
||||||
* with the model's response to the database. The new running summary is
|
* with the model's response to the database. The new running summary is
|
||||||
@@ -174,8 +170,7 @@ export const chat = router({
|
|||||||
mainResponseContent: mainResponse.text,
|
mainResponseContent: mainResponse.text,
|
||||||
previousRunningSummary,
|
previousRunningSummary,
|
||||||
});
|
});
|
||||||
const insertedAssistantMessage: CommittedMessage = {
|
const insertedAssistantMessage = await db.messages.create({
|
||||||
id: nanoid(),
|
|
||||||
conversationId,
|
conversationId,
|
||||||
// content: mainResponse.text,
|
// content: mainResponse.text,
|
||||||
parts: [{ type: "text", text: mainResponse.text }],
|
parts: [{ type: "text", text: mainResponse.text }],
|
||||||
@@ -183,8 +178,7 @@ export const chat = router({
|
|||||||
role: "assistant" as const,
|
role: "assistant" as const,
|
||||||
index: messages.length,
|
index: messages.length,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
};
|
});
|
||||||
await _db.messages.create(insertedAssistantMessage);
|
|
||||||
/** Extract Facts from the model's response, and add them to the database,
|
/** Extract Facts from the model's response, and add them to the database,
|
||||||
* linking the Facts with the messages they came from. */
|
* linking the Facts with the messages they came from. */
|
||||||
const factsFromAssistantMessageResponse =
|
const factsFromAssistantMessageResponse =
|
||||||
@@ -200,15 +194,14 @@ export const chat = router({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const insertedFactsFromAssistantMessage: Array<Fact> =
|
const insertedFactsFromAssistantMessage = await db.facts.createMany(
|
||||||
factsFromAssistantMessageResponse.object.facts.map((factContent) => ({
|
factsFromAssistantMessageResponse.object.facts.map((factContent) => ({
|
||||||
id: nanoid(),
|
userId: "019900bb-61b3-7333-b760-b27784dfe33b",
|
||||||
userId: "1",
|
|
||||||
sourceMessageId: insertedAssistantMessage.id,
|
sourceMessageId: insertedAssistantMessage.id,
|
||||||
content: factContent,
|
content: factContent,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
}));
|
}))
|
||||||
_db.facts.createMany(insertedFactsFromAssistantMessage);
|
);
|
||||||
|
|
||||||
const insertedFacts = [
|
const insertedFacts = [
|
||||||
...insertedFactsFromUserMessage,
|
...insertedFactsFromUserMessage,
|
||||||
@@ -227,9 +220,8 @@ export const chat = router({
|
|||||||
messagesSincePreviousRunningSummary,
|
messagesSincePreviousRunningSummary,
|
||||||
fact,
|
fact,
|
||||||
});
|
});
|
||||||
const insertedFactTriggers: Array<FactTrigger> =
|
const insertedFactTriggers: Array<Omit<FactTrigger, "id">> =
|
||||||
factTriggers.object.factTriggers.map((factTrigger) => ({
|
factTriggers.object.factTriggers.map((factTrigger) => ({
|
||||||
id: nanoid(),
|
|
||||||
sourceFactId: fact.id,
|
sourceFactId: fact.id,
|
||||||
content: factTrigger,
|
content: factTrigger,
|
||||||
priorityMultiplier: 1,
|
priorityMultiplier: 1,
|
||||||
@@ -237,10 +229,10 @@ export const chat = router({
|
|||||||
scopeConversationId: conversationId,
|
scopeConversationId: conversationId,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
}));
|
}));
|
||||||
_db.factTriggers.createMany(insertedFactTriggers);
|
db.factTriggers.createMany(insertedFactTriggers);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.write();
|
// await db.write();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
insertedAssistantMessage,
|
insertedAssistantMessage,
|
||||||
|
|||||||
Generated
+3
@@ -117,6 +117,9 @@ importers:
|
|||||||
'@hono/vite-dev-server':
|
'@hono/vite-dev-server':
|
||||||
specifier: ^0.19.1
|
specifier: ^0.19.1
|
||||||
version: 0.19.1(hono@4.8.3)(miniflare@4.20250617.4)(wrangler@4.22.0(@cloudflare/workers-types@4.20250627.0))
|
version: 0.19.1(hono@4.8.3)(miniflare@4.20250617.4)(wrangler@4.22.0(@cloudflare/workers-types@4.20250627.0))
|
||||||
|
'@kristiandupont/recase':
|
||||||
|
specifier: ^1.4.1
|
||||||
|
version: 1.4.1
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.19.0
|
specifier: ^20.19.0
|
||||||
version: 20.19.1
|
version: 20.19.1
|
||||||
|
|||||||
+16
-12
@@ -17,12 +17,12 @@ const env: Record<string, string | undefined> =
|
|||||||
typeof process?.env !== "undefined"
|
typeof process?.env !== "undefined"
|
||||||
? process.env
|
? process.env
|
||||||
: import.meta && "env" in import.meta
|
: import.meta && "env" in import.meta
|
||||||
? (
|
? (
|
||||||
import.meta as ImportMeta & {
|
import.meta as ImportMeta & {
|
||||||
env: Record<string, string | undefined>;
|
env: Record<string, string | undefined>;
|
||||||
}
|
}
|
||||||
).env
|
).env
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
if (!globalThis.crypto) {
|
if (!globalThis.crypto) {
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +30,7 @@ if (!globalThis.crypto) {
|
|||||||
*/
|
*/
|
||||||
Object.defineProperty(globalThis, "crypto", {
|
Object.defineProperty(globalThis, "crypto", {
|
||||||
value: await import("node:crypto").then(
|
value: await import("node:crypto").then(
|
||||||
(crypto) => crypto.webcrypto as Crypto,
|
(crypto) => crypto.webcrypto as Crypto
|
||||||
),
|
),
|
||||||
writable: false,
|
writable: false,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
@@ -40,7 +40,7 @@ if (!globalThis.crypto) {
|
|||||||
const authjsConfig = {
|
const authjsConfig = {
|
||||||
basePath: "/api/auth",
|
basePath: "/api/auth",
|
||||||
trustHost: Boolean(
|
trustHost: Boolean(
|
||||||
env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production",
|
env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production"
|
||||||
),
|
),
|
||||||
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
|
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
|
||||||
secret: "MY_SECRET",
|
secret: "MY_SECRET",
|
||||||
@@ -54,7 +54,11 @@ const authjsConfig = {
|
|||||||
},
|
},
|
||||||
async authorize() {
|
async authorize() {
|
||||||
// Add logic here to look up the user from the credentials supplied
|
// Add logic here to look up the user from the credentials supplied
|
||||||
const user = { id: "1", name: "J Smith", email: "jsmith@example.com" };
|
const user = {
|
||||||
|
id: "019900bb-61b3-7333-b760-b27784dfe33b",
|
||||||
|
name: "J Smith",
|
||||||
|
email: "jsmith@example.com",
|
||||||
|
};
|
||||||
|
|
||||||
// Any object returned will be saved in `user` property of the JWT
|
// Any object returned will be saved in `user` property of the JWT
|
||||||
// If you return null then an error will be displayed advising the user to check their details.
|
// If you return null then an error will be displayed advising the user to check their details.
|
||||||
@@ -70,7 +74,7 @@ const authjsConfig = {
|
|||||||
*/
|
*/
|
||||||
export async function getSession(
|
export async function getSession(
|
||||||
req: Request,
|
req: Request,
|
||||||
config: Omit<AuthConfig, "raw">,
|
config: Omit<AuthConfig, "raw">
|
||||||
): Promise<Session | null> {
|
): Promise<Session | null> {
|
||||||
setEnvDefaults(process.env, config);
|
setEnvDefaults(process.env, config);
|
||||||
const requestURL = new URL(req.url);
|
const requestURL = new URL(req.url);
|
||||||
@@ -79,12 +83,12 @@ export async function getSession(
|
|||||||
requestURL.protocol,
|
requestURL.protocol,
|
||||||
req.headers,
|
req.headers,
|
||||||
process.env,
|
process.env,
|
||||||
config,
|
config
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await Auth(
|
const response = await Auth(
|
||||||
new Request(url, { headers: { cookie: req.headers.get("cookie") ?? "" } }),
|
new Request(url, { headers: { cookie: req.headers.get("cookie") ?? "" } }),
|
||||||
config,
|
config
|
||||||
);
|
);
|
||||||
|
|
||||||
const { status = 200 } = response;
|
const { status = 200 } = response;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { UIMessage } from "ai";
|
import type { UIMessage } from "ai";
|
||||||
import type { generateText } from "ai";
|
import type { generateText } from "ai";
|
||||||
import type { Conversation, Fact, FactTrigger } from "./database/lowdb.js";
|
import type { Conversation, Fact, FactTrigger } from "./database/common";
|
||||||
|
|
||||||
export type OtherParameters = Omit<
|
export type OtherParameters = Omit<
|
||||||
Parameters<typeof generateText>[0],
|
Parameters<typeof generateText>[0],
|
||||||
|
|||||||
Reference in New Issue
Block a user