|
@@ -1135,6 +1135,7 @@ export type Store = {
|
|
|
insertContent: (hash: string, content: string, createdAt: string) => void;
|
|
insertContent: (hash: string, content: string, createdAt: string) => void;
|
|
|
insertDocument: (collectionName: string, path: string, title: string, hash: string, createdAt: string, modifiedAt: string) => void;
|
|
insertDocument: (collectionName: string, path: string, title: string, hash: string, createdAt: string, modifiedAt: string) => void;
|
|
|
findActiveDocument: (collectionName: string, path: string) => { id: number; hash: string; title: string } | null;
|
|
findActiveDocument: (collectionName: string, path: string) => { id: number; hash: string; title: string } | null;
|
|
|
|
|
+ findOrMigrateLegacyDocument: (collectionName: string, path: string) => { id: number; hash: string; title: string } | null;
|
|
|
updateDocumentTitle: (documentId: number, title: string, modifiedAt: string) => void;
|
|
updateDocumentTitle: (documentId: number, title: string, modifiedAt: string) => void;
|
|
|
updateDocument: (documentId: number, title: string, hash: string, modifiedAt: string) => void;
|
|
updateDocument: (documentId: number, title: string, hash: string, modifiedAt: string) => void;
|
|
|
deactivateDocument: (collectionName: string, path: string) => void;
|
|
deactivateDocument: (collectionName: string, path: string) => void;
|
|
@@ -1225,7 +1226,7 @@ export async function reindexCollection(
|
|
|
const hash = await hashContent(content);
|
|
const hash = await hashContent(content);
|
|
|
const title = extractTitle(content, relativeFile);
|
|
const title = extractTitle(content, relativeFile);
|
|
|
|
|
|
|
|
- const existing = findActiveDocument(db, collectionName, path);
|
|
|
|
|
|
|
+ const existing = findOrMigrateLegacyDocument(db, collectionName, path);
|
|
|
|
|
|
|
|
if (existing) {
|
|
if (existing) {
|
|
|
if (existing.hash === hash) {
|
|
if (existing.hash === hash) {
|
|
@@ -1648,6 +1649,7 @@ export function createStore(dbPath?: string): Store {
|
|
|
insertContent: (hash: string, content: string, createdAt: string) => insertContent(db, hash, content, createdAt),
|
|
insertContent: (hash: string, content: string, createdAt: string) => insertContent(db, hash, content, createdAt),
|
|
|
insertDocument: (collectionName: string, path: string, title: string, hash: string, createdAt: string, modifiedAt: string) => insertDocument(db, collectionName, path, title, hash, createdAt, modifiedAt),
|
|
insertDocument: (collectionName: string, path: string, title: string, hash: string, createdAt: string, modifiedAt: string) => insertDocument(db, collectionName, path, title, hash, createdAt, modifiedAt),
|
|
|
findActiveDocument: (collectionName: string, path: string) => findActiveDocument(db, collectionName, path),
|
|
findActiveDocument: (collectionName: string, path: string) => findActiveDocument(db, collectionName, path),
|
|
|
|
|
+ findOrMigrateLegacyDocument: (collectionName: string, path: string) => findOrMigrateLegacyDocument(db, collectionName, path),
|
|
|
updateDocumentTitle: (documentId: number, title: string, modifiedAt: string) => updateDocumentTitle(db, documentId, title, modifiedAt),
|
|
updateDocumentTitle: (documentId: number, title: string, modifiedAt: string) => updateDocumentTitle(db, documentId, title, modifiedAt),
|
|
|
updateDocument: (documentId: number, title: string, hash: string, modifiedAt: string) => updateDocument(db, documentId, title, hash, modifiedAt),
|
|
updateDocument: (documentId: number, title: string, hash: string, modifiedAt: string) => updateDocument(db, documentId, title, hash, modifiedAt),
|
|
|
deactivateDocument: (collectionName: string, path: string) => deactivateDocument(db, collectionName, path),
|
|
deactivateDocument: (collectionName: string, path: string) => deactivateDocument(db, collectionName, path),
|
|
@@ -2102,6 +2104,57 @@ export function findActiveDocument(
|
|
|
return row ?? null;
|
|
return row ?? null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Find an active document, falling back to a legacy lowercase path.
|
|
|
|
|
+ * If found under the legacy path, renames it in-place and rebuilds the
|
|
|
|
|
+ * FTS entry. Embeddings are keyed by content hash, so the rename is
|
|
|
|
|
+ * safe — no re-embedding required.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @internal Used by reindexCollection and indexFiles during qmd update.
|
|
|
|
|
+ * Returns null if the document does not exist under either path.
|
|
|
|
|
+ */
|
|
|
|
|
+export function findOrMigrateLegacyDocument(
|
|
|
|
|
+ db: Database,
|
|
|
|
|
+ collectionName: string,
|
|
|
|
|
+ path: string
|
|
|
|
|
+): { id: number; hash: string; title: string } | null {
|
|
|
|
|
+ const existing = findActiveDocument(db, collectionName, path);
|
|
|
|
|
+ if (existing) return existing;
|
|
|
|
|
+
|
|
|
|
|
+ const legacyPath = path.toLowerCase();
|
|
|
|
|
+ if (legacyPath === path) return null;
|
|
|
|
|
+
|
|
|
|
|
+ const legacy = findActiveDocument(db, collectionName, legacyPath);
|
|
|
|
|
+ if (!legacy) return null;
|
|
|
|
|
+
|
|
|
|
|
+ // Wrap rename + FTS rebuild in a transaction for atomicity.
|
|
|
|
|
+ const migrate = db.transaction(() => {
|
|
|
|
|
+ // Use OR IGNORE so a UNIQUE conflict (e.g. both "readme.md" and
|
|
|
|
|
+ // "README.md" already exist) is a no-op rather than crashing.
|
|
|
|
|
+ const result = db.prepare(
|
|
|
|
|
+ `UPDATE OR IGNORE documents SET path = ? WHERE id = ? AND active = 1`
|
|
|
|
|
+ ).run(path, legacy.id);
|
|
|
|
|
+
|
|
|
|
|
+ if (result.changes === 0) return false;
|
|
|
|
|
+
|
|
|
|
|
+ // FTS5 does not reliably update via the documents_au trigger's
|
|
|
|
|
+ // INSERT OR REPLACE. Manually rebuild the FTS entry.
|
|
|
|
|
+ db.prepare(`DELETE FROM documents_fts WHERE rowid = ?`).run(legacy.id);
|
|
|
|
|
+ db.prepare(`
|
|
|
|
|
+ INSERT INTO documents_fts(rowid, filepath, title, body)
|
|
|
|
|
+ SELECT id, collection || '/' || path, title,
|
|
|
|
|
+ (SELECT doc FROM content WHERE hash = documents.hash)
|
|
|
|
|
+ FROM documents WHERE id = ?
|
|
|
|
|
+ `).run(legacy.id);
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!migrate()) return null;
|
|
|
|
|
+
|
|
|
|
|
+ return findActiveDocument(db, collectionName, path);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Update the title and modified_at timestamp for a document.
|
|
* Update the title and modified_at timestamp for a document.
|
|
|
*/
|
|
*/
|