LocalLens
Walkthrough

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á

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 qué existe este archivo

Dos razones:

  1. Fija la forma de los datos que se mueven entre módulos. Brain, TextChunk, SearchHit y ChatAnswer los toca cada otro archivo. Ponerlos en un solo lugar evita que tres módulos tengan ideas ligeramente distintas de cómo se ve un TextChunk.
  2. Mantiene AppError reutilizable. Cualquier módulo puede arrojar un AppError con 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.

  • LocalDocument carga un checksum porque el walker de carpetas locales puede computarlo desde el disco.
  • BrowserDocumentInput no — 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 SearchHit es lo que ragSearch devuelve. El contenido del chunk está incluido porque el builder del prompt lo necesita.
  • Una Citation es 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.

On this page