2026-08-07 — marvelousdb: migracion a SQLite completo (FTS5)

Contexto: el usuario pidio unificar los personajes en una base .sqlite para “mejor consumo y portabilidad”. Se eligio la opcion SQLite completo con FTS5.

Decisiones

  • Motor: node:sqlite (DatabaseSync) de Node >= 23.4 — cero dependencias nativas. SQLite 3.50.4. Warning experimental silenciado con --disable-warning=ExperimentalWarning en los npm scripts.
  • Esquema (data/build-sqlite.js):
    • characters(id, name, subtitle, thumbnail, gender, universe, data) — data es el JSON completo; name/subtitle/gender/universe son columnas indexadas.
    • comics(id, title, subtitle, thumbnail, data).
    • FTS5 virtual tables characters_fts y comics_fts con tokenizer trigram (substring matching, case-insensitive) + bm25() ranking.
  • Búsqueda: MATCH con frases por token (3+ chars; trigram no matchea queries cortas → fallback LIKE sobre name/title). field se mapea a columnas FTS (wiki.real_name → real_name OR aliases). Filtros gender/universe por SQL con bind. Nada en memoria.
  • Build: npm run build:sqlite (o auto-build en primer arranque de la API). Escanea data/ y data/pdsh/. .db en .gitignore (reconstruible).
  • Dependencias: eliminado FlexSearch (ya no se usa).

Resultados

  • .db: 10,819 personajes + 30,179 comics, 268.9 MB (vs 361 MB JSON), build en 11.1s.
  • Arranque API: 0.5s (antes ~7s indexando 40k JSON). RSS: 66 MB (antes ~500 MB).
  • 15/15 tests verdes (misma firma de API; el test child ya no necesita DATA_DIR, el .db contiene ambos datasets).
  • Todas las rutas 200 (home, /vs, búsquedas, detalles).

Bugs encontrados

  • “column index out of range”: bind() metia limit/offset en params Y se pasaban sueltos → doble. Fix: LIMIT/OFFSET con ’?’ literal.
  • “no such column: issueNumber”: la tabla comics no tiene esa columna (está en el JSON). Fix: quitar del SELECT del listado; el detail la lee del JSON.
  • “ambiguous column name: universe”: characters_fts comparte nombre de columna → calificar filtros con c. en los JOINs.
  • thumbnails null en comic detail: mismatch Number/String en Map keys → normalizar a String.

Pendientes

  • Si el .db crece mucho: external-content FTS o trigram solo en campos relevantes.
  • El usuario tiene el proceso viejo corriendo; reiniciar npm start para ver SQLite.