index.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { DatabaseSync } from 'node:sqlite'
  2. import { promises as fs } from 'node:fs'
  3. import path from 'node:path'
  4. import { SCHEMA_SQL } from './schema.js'
  5. import { rebuildCache } from './rebuilder.js'
  6. /**
  7. * CacheManager:管理 .cache/index.db 五表。
  8. */
  9. export class CacheManager {
  10. constructor(dbPath) {
  11. this.dbPath = dbPath
  12. this.db = null
  13. }
  14. /**
  15. * 确保数据库就绪(不存在或损坏 → 重建)。
  16. * @param {string} repoPath - 书仓库根目录
  17. * @returns {Promise<void>}
  18. */
  19. async ensureReady(repoPath) {
  20. // 检查 db 文件是否存在
  21. try {
  22. await fs.access(this.dbPath)
  23. } catch (err) {
  24. // 不存在,先创建目录
  25. const dir = path.dirname(this.dbPath)
  26. await fs.mkdir(dir, { recursive: true })
  27. // 重建
  28. return this.rebuildFromSource(repoPath)
  29. }
  30. // 打开现有数据库
  31. try {
  32. this.db = new DatabaseSync(this.dbPath)
  33. // 验证表是否存在
  34. const tables = this.db
  35. .prepare("SELECT name FROM sqlite_master WHERE type='table'")
  36. .all()
  37. if (tables.length === 0) {
  38. // 空数据库,重建
  39. this.db.close()
  40. return this.rebuildFromSource(repoPath)
  41. }
  42. } catch (err) {
  43. // 损坏,重建
  44. return this.rebuildFromSource(repoPath)
  45. }
  46. }
  47. /**
  48. * 全量重建缓存。
  49. * @param {string} repoPath
  50. * @returns {Promise<{ok: boolean, warnings: string[], errors: string[]}>}
  51. */
  52. async rebuildFromSource(repoPath) {
  53. // 关闭现有连接
  54. if (this.db) {
  55. this.db.close()
  56. this.db = null
  57. }
  58. // 删除旧数据库
  59. try {
  60. await fs.unlink(this.dbPath)
  61. } catch (err) {
  62. // 文件不存在,忽略
  63. }
  64. // 确保 .cache 目录存在:rebuildFromSource 可被直接调用,不保证先过 ensureReady
  65. await fs.mkdir(path.dirname(this.dbPath), { recursive: true })
  66. // 创建新数据库
  67. this.db = new DatabaseSync(this.dbPath)
  68. // 执行 DDL
  69. this.db.exec(SCHEMA_SQL)
  70. // 调用重建器
  71. const result = await rebuildCache(repoPath, this.db)
  72. return result
  73. }
  74. /**
  75. * 执行查询。
  76. * @param {string} sql
  77. * @param {any[]} params
  78. * @returns {Promise<any[]>}
  79. */
  80. async query(sql, params = []) {
  81. if (!this.db) {
  82. throw new Error('数据库未初始化')
  83. }
  84. const stmt = this.db.prepare(sql)
  85. return stmt.all(...params)
  86. }
  87. /**
  88. * 关闭数据库连接。
  89. * @returns {Promise<void>}
  90. */
  91. async close() {
  92. if (this.db) {
  93. this.db.close()
  94. this.db = null
  95. }
  96. }
  97. }