database.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { Database } from 'bun:sqlite';
  2. import { readdir, readFile } from 'fs/promises';
  3. import { join } from 'path';
  4. import { gerarEmbedding } from './insert-embeddings';
  5. import type { Documento, DocumentoComEmbedding, DocumentoBD } from './types';
  6. /**
  7. * Classe para gerenciar o banco de dados de embeddings
  8. *
  9. * Por que uma classe?
  10. * - Encapsula toda a lógica de banco de dados (Single Responsibility)
  11. * - Facilita testes e manutenção
  12. * - Permite reutilizar em outros lugares do projeto
  13. */
  14. class BancoVetorial {
  15. private db: Database;
  16. constructor(caminhoDb: string = process.env.DB_PATH || './embeddings.sqlite') {
  17. // Cria ou abre o banco de dados
  18. this.db = new Database(caminhoDb);
  19. // Inicializa as tabelas
  20. this.inicializarTabelas();
  21. }
  22. /**
  23. * Cria a tabela de documentos se ela não existir
  24. *
  25. * Por que IF NOT EXISTS?
  26. * - Permite executar múltiplas vezes sem erro
  27. * - Facilita deploy e atualizações
  28. */
  29. private inicializarTabelas(): void {
  30. this.db.run(`
  31. CREATE TABLE IF NOT EXISTS documentos (
  32. id INTEGER PRIMARY KEY AUTOINCREMENT,
  33. nome TEXT NOT NULL,
  34. caminho TEXT NOT NULL UNIQUE,
  35. conteudo TEXT NOT NULL,
  36. embedding TEXT NOT NULL,
  37. data_indexacao TEXT NOT NULL
  38. )
  39. `);
  40. console.log('Tabela de documentos inicializada');
  41. }
  42. /**
  43. * Insere ou atualiza um documento no banco
  44. *
  45. * Por que INSERT OR REPLACE?
  46. * - Se o documento já existe (mesmo caminho), atualiza
  47. * - Se não existe, insere novo
  48. * - Evita duplicatas
  49. */
  50. inserirDocumento(documento: DocumentoComEmbedding): void {
  51. // Convertemos o array de números para JSON string
  52. const embeddingJson = JSON.stringify(documento.embedding);
  53. // Data atual no formato ISO
  54. const dataIndexacao = new Date().toISOString();
  55. const stmt = this.db.prepare(`
  56. INSERT OR REPLACE INTO documentos (nome, caminho, conteudo, embedding, data_indexacao)
  57. VALUES (?, ?, ?, ?, ?)
  58. `);
  59. stmt.run(
  60. documento.nome,
  61. documento.caminho,
  62. documento.conteudo,
  63. embeddingJson,
  64. dataIndexacao
  65. );
  66. console.log(`Documento inserido: ${documento.nome}`);
  67. }
  68. /**
  69. * Insere múltiplos documentos em uma transação
  70. *
  71. * Por que transação?
  72. * - Muito mais rápido (até 100x)
  73. * - Garante atomicidade: ou todos são inseridos ou nenhum
  74. * - Se der erro no meio, faz rollback automático
  75. */
  76. inserirDocumentosEmLote(documentos: DocumentoComEmbedding[]): void {
  77. const transaction = this.db.transaction((docs: DocumentoComEmbedding[]) => {
  78. for (const doc of docs) {
  79. this.inserirDocumento(doc);
  80. }
  81. });
  82. transaction(documentos);
  83. console.log(`${documentos.length} documentos inseridos em lote`);
  84. }
  85. /**
  86. * Busca todos os documentos do banco
  87. *
  88. * Por que retornar DocumentoComEmbedding[]?
  89. * - Mantém a interface consistente com o resto do código
  90. * - Facilita reutilizar em outras partes do sistema
  91. */
  92. buscarTodosDocumentos(): DocumentoComEmbedding[] {
  93. const stmt = this.db.prepare('SELECT * FROM documentos');
  94. const rows = stmt.all() as DocumentoBD[];
  95. // Convertemos os dados do banco para a interface que usamos no código
  96. return rows.map(row => ({
  97. nome: row.nome,
  98. caminho: row.caminho,
  99. conteudo: row.conteudo,
  100. tamanho: row.conteudo.length,
  101. embedding: JSON.parse(row.embedding) // Converte JSON string de volta para array
  102. }));
  103. }
  104. /**
  105. * Conta quantos documentos estão indexados
  106. */
  107. contarDocumentos(): number {
  108. const stmt = this.db.prepare('SELECT COUNT(*) as total FROM documentos');
  109. const result = stmt.get() as { total: number };
  110. return result.total;
  111. }
  112. /**
  113. * Limpa todos os documentos do banco
  114. *
  115. * Útil para reindexar tudo do zero
  116. */
  117. limparDocumentos(): void {
  118. this.db.run('DELETE FROM documentos');
  119. console.log('Todos os documentos foram removidos');
  120. }
  121. /**
  122. * Fecha a conexão com o banco
  123. *
  124. * Por que fechar?
  125. * - Libera recursos
  126. * - Garante que todos os dados foram salvos
  127. * - Boa prática de gerenciamento de recursos
  128. */
  129. fechar(): void {
  130. this.db.close();
  131. console.log('Conexao com banco fechada');
  132. }
  133. }
  134. // Exporta para ser usado em outros arquivos
  135. export { BancoVetorial, type DocumentoComEmbedding };