1. Tipos de dominio
El vocabulario que cada otro módulo importa.
src/domain.ts es el único archivo del proyecto sin imports
internos. Es el vocabulario compartido que el resto de la app
habla.
Qué vive acá
export type Brain = {
id: string;
name: string;
folderPath: string;
workspace: string;
status: "indexing" | "indexed" | "error";
fileCount: number;
chunkCount: number;
createdAt: string;
updatedAt: string;
lastIndexedAt?: string;
lastError?: string;
};
export type LocalDocument = {
relativePath: string;
content: string;
checksum: string;
bytes: number;
};
export type BrowserDocumentInput = {
relativePath: string;
content: string;
bytes: number;
};
export type TextChunk = {
id: string;
brainId: string;
relativePath: string;
chunkIndex: number;
content: string;
checksum: string;
};
export type ChatMessage = {
role: "user" | "assistant" | "system";
content: string;
};
export type Citation = {
id: string;
relativePath: string;
chunkIndex: number;
score?: number;
};
export type SearchHit = Citation & { content: string };
export type ChatAnswer = { answer: string; citations: Citation[] };
export type CreateBrainFromFilesInput = {
name: string;
folderName: string;
documents: BrowserDocumentInput[];
};
export type CreateBrainFromFolderInput = {
name: string;
folderPath: string;
};
export class AppError extends Error {
constructor(
message: string,
readonly status = 400,
) {
super(message);
this.name = "AppError";
}
}Por qué existe este archivo
Dos razones:
- Fija la forma de los datos que se mueven entre módulos.
Brain,TextChunk,SearchHityChatAnswerlos toca cada otro archivo. Ponerlos en un solo lugar evita que tres módulos tengan ideas ligeramente distintas de cómo se ve unTextChunk. - Mantiene
AppErrorreutilizable. Cualquier módulo puede arrojar unAppErrorcon un status estilo HTTP, y el servidor lo mapea directo a una response. Sin esto, terminas con tipos de error a medida en cada capa.
La costura entre local y navegador
LocalDocument y BrowserDocumentInput están separados
deliberadamente.
LocalDocumentcarga unchecksumporque el walker de carpetas locales puede computarlo desde el disco.BrowserDocumentInputno — el file picker nos entrega contenido y bytes; el checksum lo computa después el adaptador de archivos.
Mantener estos dos tipos separados significa que el workflow no tiene que tratar "este cerebro vino del disco" vs "este cerebro vino de una subida del navegador" como caso especial hasta el último paso.
Citation tiene un score, SearchHit tiene content
SearchHit extends Citation. La separación importa:
- Un
SearchHites lo queragSearchdevuelve. El contenido del chunk está incluido porque el builder del prompt lo necesita. - Una
Citationes lo que devolvemos al caller. El contenido del chunk se descarta intencionalmente — el caller no necesita renderizar la fuente literal, solo enlazar a ella.
Un patrón chico, pero impide que el texto del chunk se filtre en responses HTTP.
Qué puedes correr
Después de escribir solo domain.ts:
bun run typecheck…va a pasar. Nada más compila aún porque nada más existe, pero el archivo de dominio por su cuenta no tiene errores.
Lo que sigue: el chunker y el prompt fundamentado, que importa estos tipos y empieza a usarlos.