1. Tipos de domínio
O vocabulário que todo outro módulo importa.
src/domain.ts é o único arquivo do projeto sem imports
internos. É o vocabulário compartilhado que o resto do app fala.
O que mora aqui
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 que esse arquivo existe
Por duas razões:
- Fixa a forma dos dados que se movem entre módulos.
Brain,TextChunk,SearchHit, eChatAnswersão tocados por todos os outros arquivos. Colocar em um lugar evita que três módulos tenham ideias ligeiramente diferentes de como umTextChunkse parece. - Mantém o
AppErrorreutilizável. Qualquer módulo pode lançar umAppErrorcom status estilo HTTP, e o servidor mapeia direto para uma resposta. Sem isso, você acaba com tipos de erro sob medida em cada camada.
A costura entre local e navegador
LocalDocument e BrowserDocumentInput são deliberadamente
separados.
LocalDocumentcarrega umchecksumporque o walker de pasta local consegue computar um a partir do disco.BrowserDocumentInputnão — o file picker nos entrega conteúdo e bytes; o checksum é computado downstream pelo adaptador de arquivo.
Manter esses dois tipos separados significa que o workflow não precisa fazer special-case para "esse brain veio do disco" vs "esse brain veio de um upload do navegador" até o último passo.
Citation tem score, SearchHit tem content
SearchHit extends Citation. A separação importa:
- Um
SearchHité o que oragSearchretorna. O conteúdo do chunk é incluído porque o construtor de prompt precisa dele. - Um
Citationé o que retornamos ao chamador. O conteúdo do chunk é dropado de propósito — o chamador não precisa renderizar a fonte literal, só linkar.
Um padrão pequeno, mas evita que texto de chunk vaze para respostas HTTP.
O que você pode rodar
Depois de escrever só o domain.ts:
bun run typecheck…vai passar. Nada mais compila ainda porque nada mais existe, mas o arquivo de domínio sozinho não tem erros.
A seguir: o chunker e prompt embasado, que importa esses tipos e começa a usar.