LocalLens
Walkthrough

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

src/domain.ts
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:

  1. Fixa a forma dos dados que se movem entre módulos. Brain, TextChunk, SearchHit, e ChatAnswer são tocados por todos os outros arquivos. Colocar em um lugar evita que três módulos tenham ideias ligeiramente diferentes de como um TextChunk se parece.
  2. Mantém o AppError reutilizável. Qualquer módulo pode lançar um AppError com 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.

  • LocalDocument carrega um checksum porque o walker de pasta local consegue computar um a partir do disco.
  • BrowserDocumentInput nã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 o ragSearch retorna. 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.

On this page