db.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. /**
  2. * db.ts - Cross-runtime SQLite compatibility layer
  3. *
  4. * Provides a unified Database export that works under both Bun (bun:sqlite)
  5. * and Node.js (better-sqlite3). The APIs are nearly identical — the main
  6. * difference is the import path.
  7. *
  8. * On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
  9. * which prevents loading native extensions like sqlite-vec. When running under
  10. * Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
  11. * SQLite build before creating any database instances.
  12. */
  13. export const isBun = typeof globalThis.Bun !== "undefined";
  14. let _Database;
  15. let _sqliteVecLoad;
  16. if (isBun) {
  17. // Dynamic string prevents tsc from resolving bun:sqlite on Node.js builds
  18. const bunSqlite = "bun:" + "sqlite";
  19. const BunDatabase = (await import(/* @vite-ignore */ bunSqlite)).Database;
  20. // See: https://bun.com/docs/runtime/sqlite#setcustomsqlite
  21. if (process.platform === "darwin") {
  22. const homebrewPaths = [
  23. "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib", // Apple Silicon
  24. "/usr/local/opt/sqlite/lib/libsqlite3.dylib", // Intel
  25. ];
  26. for (const p of homebrewPaths) {
  27. try {
  28. BunDatabase.setCustomSQLite(p);
  29. break;
  30. }
  31. catch { }
  32. }
  33. }
  34. _Database = BunDatabase;
  35. // setCustomSQLite may have silently failed — test that extensions actually work.
  36. try {
  37. const { getLoadablePath } = await import("sqlite-vec");
  38. const vecPath = getLoadablePath();
  39. const testDb = new BunDatabase(":memory:");
  40. testDb.loadExtension(vecPath);
  41. testDb.close();
  42. _sqliteVecLoad = (db) => db.loadExtension(vecPath);
  43. }
  44. catch {
  45. // Vector search won't work, but BM25 and other operations are unaffected.
  46. _sqliteVecLoad = null;
  47. }
  48. }
  49. else {
  50. _Database = (await import("better-sqlite3")).default;
  51. const sqliteVec = await import("sqlite-vec");
  52. _sqliteVecLoad = (db) => sqliteVec.load(db);
  53. }
  54. /**
  55. * Open a SQLite database. Works with both bun:sqlite and better-sqlite3.
  56. */
  57. export function openDatabase(path) {
  58. return new _Database(path);
  59. }
  60. /**
  61. * Load the sqlite-vec extension into a database.
  62. *
  63. * Throws with platform-specific fix instructions when the extension is
  64. * unavailable.
  65. */
  66. export function loadSqliteVec(db) {
  67. if (!_sqliteVecLoad) {
  68. const hint = isBun && process.platform === "darwin"
  69. ? "On macOS with Bun, install Homebrew SQLite: brew install sqlite\n" +
  70. "Or install qmd with npm instead: npm install -g @tobilu/qmd"
  71. : "Ensure the sqlite-vec native module is installed correctly.";
  72. throw new Error(`sqlite-vec extension is unavailable. ${hint}`);
  73. }
  74. _sqliteVecLoad(db);
  75. }