Refactor: Move collection DB operations to store.ts
- Added listCollections(), removeCollection(), renameCollection() to store.ts - Updated qmd.ts to use store methods instead of direct DB operations - Removed ~15 direct DB calls from qmd.ts collection functions - All 9 collection command tests passing Part of larger refactoring to move all DB operations to store.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6470497f2c
commit
094fa64bee
@ -1,3 +1,4 @@
|
||||
{"id":"qmd-29c","title":"Move all database operations from qmd.ts to store.ts","description":"Currently qmd.ts has ~70 direct database operations (db.prepare, db.exec). All database operations should be moved to store.ts to improve separation of concerns. qmd.ts should only use high-level methods from store.ts that don't require direct SQL knowledge.","status":"in_progress","priority":2,"issue_type":"task","created_at":"2025-12-12T16:32:13.722223-05:00","updated_at":"2025-12-12T16:32:25.423644-05:00"}
|
||||
{"id":"qmd-4ru","title":"Update document retrieval for new schema","description":"Functions like getDocument, findDocument, getMultipleDocuments need to work with new schema (path instead of filepath, content joins, virtual paths).","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-12T15:29:53.911881-05:00","updated_at":"2025-12-12T15:56:11.054888-05:00","closed_at":"2025-12-12T15:56:11.054888-05:00","dependencies":[{"issue_id":"qmd-4ru","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.912607-05:00","created_by":"daemon"}]}
|
||||
{"id":"qmd-afe","title":"implement qmd collection rename, which changes the global path prefix for the collection","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:55:54.779325-05:00","updated_at":"2025-12-12T16:29:24.153196-05:00","closed_at":"2025-12-12T16:29:24.153196-05:00"}
|
||||
{"id":"qmd-ama","title":"Refactor database system","description":"All documents should be stored as content addressable hash, e.g. hash, doc, created_at,\n┃ updated_at. documents should be a file system layer on top e.g. collection, path, hash,\n┃ created_at, updated_at. (collection,path)\n┃\n┃\n\n┃ All documents should be stored as content addressable hash, e.g. hash, doc, created_at,\n┃ updated_at. documents should be a file system layer on top e.g. collection_id, path, hash,\n┃ created_at, updated_at. (collection,path) is unique. There is also collection which stores PWD\n┃ + glob pattern, name (\\w+). Every document is treated as path qmd://collection.name/","notes":"## Completed\n- ✅ Implemented content-addressable storage (content table with hash→doc mapping)\n- ✅ Refactored documents table as file system layer (collection_id, path, hash)\n- ✅ Added collection names (e.g., \"pages\", \"journals\", \"archive\")\n- ✅ Implemented virtual paths (qmd://collection-name/path/to/file.md)\n- ✅ Added hierarchical context support (collection-scoped)\n- ✅ Successfully migrated existing database\n- ✅ Updated search functions to work with new schema\n- ✅ Updated indexing logic to use content-addressable storage\n- ✅ Orphaned content hash cleanup\n\n## Still TODO\n- Fix migration SQL to properly extract basename (currently needs manual fix)\n- Implement `qmd collection add . --name \u003cname\u003e --mask '**/*.md'`\n- Implement `qmd ls [path]` for exploring virtual file tree","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-10T10:57:35.497489-05:00","updated_at":"2025-12-12T15:39:48.879143-05:00","closed_at":"2025-12-12T15:39:48.879143-05:00"}
|
||||
|
||||
59
qmd.ts
59
qmd.ts
@ -20,6 +20,9 @@ import {
|
||||
getContextForPath,
|
||||
getCollectionIdByName,
|
||||
getCollectionByName,
|
||||
listCollections,
|
||||
removeCollection,
|
||||
renameCollection,
|
||||
findSimilarFiles,
|
||||
matchFilesByGlob,
|
||||
getHashesNeedingEmbedding,
|
||||
@ -1250,29 +1253,7 @@ function listFiles(pathArg?: string): void {
|
||||
// Collection management commands
|
||||
function collectionList(): void {
|
||||
const db = getDb();
|
||||
|
||||
const collections = db.prepare(`
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.pwd,
|
||||
c.glob_pattern,
|
||||
c.created_at,
|
||||
c.updated_at,
|
||||
COUNT(d.id) as file_count
|
||||
FROM collections c
|
||||
LEFT JOIN documents d ON d.collection_id = c.id AND d.active = 1
|
||||
GROUP BY c.id
|
||||
ORDER BY c.name
|
||||
`).all() as {
|
||||
id: number;
|
||||
name: string;
|
||||
pwd: string;
|
||||
glob_pattern: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
file_count: number;
|
||||
}[];
|
||||
const collections = listCollections(db);
|
||||
|
||||
if (collections.length === 0) {
|
||||
console.log("No collections found. Run 'qmd add .' to create one.");
|
||||
@ -1289,7 +1270,7 @@ function collectionList(): void {
|
||||
console.log(`${c.cyan}${coll.name}${c.reset}`);
|
||||
console.log(` ${c.dim}Path:${c.reset} ${coll.pwd}`);
|
||||
console.log(` ${c.dim}Pattern:${c.reset} ${coll.glob_pattern}`);
|
||||
console.log(` ${c.dim}Files:${c.reset} ${coll.file_count}`);
|
||||
console.log(` ${c.dim}Files:${c.reset} ${coll.active_count}`);
|
||||
console.log(` ${c.dim}Updated:${c.reset} ${timeAgo}`);
|
||||
console.log();
|
||||
}
|
||||
@ -1349,30 +1330,12 @@ function collectionRemove(name: string): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get file count
|
||||
const fileCount = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM documents WHERE collection_id = ? AND active = 1
|
||||
`).get(coll.id) as { count: number };
|
||||
|
||||
// Delete documents
|
||||
db.prepare(`DELETE FROM documents WHERE collection_id = ?`).run(coll.id);
|
||||
|
||||
// Delete contexts
|
||||
db.prepare(`DELETE FROM path_contexts WHERE collection_id = ?`).run(coll.id);
|
||||
|
||||
// Delete collection
|
||||
db.prepare(`DELETE FROM collections WHERE id = ?`).run(coll.id);
|
||||
|
||||
// Clean up orphaned content hashes
|
||||
const cleanupResult = db.prepare(`
|
||||
DELETE FROM content
|
||||
WHERE hash NOT IN (SELECT DISTINCT hash FROM documents WHERE active = 1)
|
||||
`).run();
|
||||
const result = removeCollection(db, coll.id);
|
||||
|
||||
console.log(`${c.green}✓${c.reset} Removed collection '${name}'`);
|
||||
console.log(` Deleted ${fileCount.count} documents`);
|
||||
if (cleanupResult.changes > 0) {
|
||||
console.log(` Cleaned up ${cleanupResult.changes} orphaned content hashes`);
|
||||
console.log(` Deleted ${result.deletedDocs} documents`);
|
||||
if (result.cleanedHashes > 0) {
|
||||
console.log(` Cleaned up ${result.cleanedHashes} orphaned content hashes`);
|
||||
}
|
||||
|
||||
closeDb();
|
||||
@ -1399,9 +1362,7 @@ function collectionRename(oldName: string, newName: string): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Update the collection name
|
||||
db.prepare(`UPDATE collections SET name = ?, updated_at = ? WHERE id = ?`)
|
||||
.run(newName, new Date().toISOString(), coll.id);
|
||||
renameCollection(db, coll.id, newName);
|
||||
|
||||
console.log(`${c.green}✓${c.reset} Renamed collection '${oldName}' to '${newName}'`);
|
||||
console.log(` Virtual paths updated: ${c.cyan}qmd://${oldName}/${c.reset} → ${c.cyan}qmd://${newName}/${c.reset}`);
|
||||
|
||||
42
store.ts
42
store.ts
@ -1076,6 +1076,48 @@ export function getCollectionByName(db: Database, name: string): { id: number; n
|
||||
return result;
|
||||
}
|
||||
|
||||
export function listCollections(db: Database): { id: number; name: string; pwd: string; glob_pattern: string; created_at: string; updated_at: string; doc_count: number; active_count: number; last_modified: string | null }[] {
|
||||
const collections = db.prepare(`
|
||||
SELECT c.id, c.name, c.pwd, c.glob_pattern, c.created_at, c.updated_at,
|
||||
COUNT(d.id) as doc_count,
|
||||
SUM(CASE WHEN d.active = 1 THEN 1 ELSE 0 END) as active_count,
|
||||
MAX(d.modified_at) as last_modified
|
||||
FROM collections c
|
||||
LEFT JOIN documents d ON d.collection_id = c.id
|
||||
GROUP BY c.id
|
||||
ORDER BY c.name
|
||||
`).all() as { id: number; name: string; pwd: string; glob_pattern: string; created_at: string; updated_at: string; doc_count: number; active_count: number; last_modified: string | null }[];
|
||||
return collections;
|
||||
}
|
||||
|
||||
export function removeCollection(db: Database, collectionId: number): { deletedDocs: number; cleanedHashes: number } {
|
||||
// Delete documents
|
||||
const docResult = db.prepare(`DELETE FROM documents WHERE collection_id = ?`).run(collectionId);
|
||||
|
||||
// Delete contexts
|
||||
db.prepare(`DELETE FROM path_contexts WHERE collection_id = ?`).run(collectionId);
|
||||
|
||||
// Delete collection
|
||||
db.prepare(`DELETE FROM collections WHERE id = ?`).run(collectionId);
|
||||
|
||||
// Clean up orphaned content hashes
|
||||
const cleanupResult = db.prepare(`
|
||||
DELETE FROM content
|
||||
WHERE hash NOT IN (SELECT DISTINCT hash FROM documents WHERE active = 1)
|
||||
`).run();
|
||||
|
||||
return {
|
||||
deletedDocs: docResult.changes,
|
||||
cleanedHashes: cleanupResult.changes
|
||||
};
|
||||
}
|
||||
|
||||
export function renameCollection(db: Database, collectionId: number, newName: string): void {
|
||||
const now = new Date().toISOString();
|
||||
db.prepare(`UPDATE collections SET name = ?, updated_at = ? WHERE id = ?`)
|
||||
.run(newName, now, collectionId);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FTS Search
|
||||
// =============================================================================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user