UNPKG

roam-research-mcp

Version:
94 lines (93 loc) • 4.35 kB
import { q } from '@roam-research/roam-api-sdk'; import { BaseSearchHandler } from './types.js'; import { SearchUtils } from './utils.js'; export class TagSearchHandler extends BaseSearchHandler { constructor(graph, params) { super(graph); this.params = params; } async execute() { const { primary_tag, page_title_uid, near_tag, exclude_tag, case_sensitive = false, limit = -1, offset = 0 } = this.params; let nearTagUid; if (near_tag) { nearTagUid = await SearchUtils.findPageByTitleOrUid(this.graph, near_tag); if (!nearTagUid) { return { success: false, matches: [], message: `Near tag "${near_tag}" not found.`, total_count: 0 }; } } let excludeTagUid; if (exclude_tag) { excludeTagUid = await SearchUtils.findPageByTitleOrUid(this.graph, exclude_tag); if (!excludeTagUid) { return { success: false, matches: [], message: `Exclude tag "${exclude_tag}" not found.`, total_count: 0 }; } } // Get target page UID if provided for scoped search let targetPageUid; if (page_title_uid) { targetPageUid = await SearchUtils.findPageByTitleOrUid(this.graph, page_title_uid); } const tagMatchClause = SearchUtils.buildTagMatchClause(primary_tag, '?ref-page', case_sensitive); let inClause = `:in $`; let queryLimit = limit === -1 ? '' : `:limit ${limit}`; let queryOffset = offset === 0 ? '' : `:offset ${offset}`; let queryOrder = `:order ?page-edit-time asc ?block-uid asc`; // Sort by page edit time, then block UID let queryWhereClauses = ` ${tagMatchClause} [?b :block/refs ?ref-page] [?b :block/string ?block-str] [?b :block/uid ?block-uid] [?b :block/page ?p] [?p :node/title ?page-title] [?p :edit/time ?page-edit-time] [(get-else $ ?b :create/time 0) ?block-create-time] [(get-else $ ?b :edit/time 0) ?block-edit-time]`; // Fetch page edit time for sorting, block timestamps for sort/group if (nearTagUid) { queryWhereClauses += ` [?b :block/refs ?near-tag-page] [?near-tag-page :block/uid "${nearTagUid}"]`; } if (excludeTagUid) { queryWhereClauses += ` (not [?b :block/refs ?exclude-tag-page]) [?exclude-tag-page :block/uid "${excludeTagUid}"]`; } if (targetPageUid) { inClause += ` ?target-page-uid`; queryWhereClauses += ` [?p :block/uid ?target-page-uid]`; } const queryStr = `[:find ?block-uid ?block-str ?page-title ?block-create-time ?block-edit-time ${inClause} ${queryLimit} ${queryOffset} ${queryOrder} :where ${queryWhereClauses}]`; const queryArgs = []; if (targetPageUid) { queryArgs.push(targetPageUid); } const rawResults = await q(this.graph, queryStr, queryArgs); // Query to get total count without limit const countQueryStr = `[:find (count ?b) ${inClause} :where ${queryWhereClauses.replace(/\[\?p :edit\/time \?page-edit-time\]/, '')}]`; // Remove edit time for count query const totalCountResults = await q(this.graph, countQueryStr, queryArgs); const totalCount = totalCountResults[0] ? totalCountResults[0][0] : 0; // Resolve block references in content const resolvedResults = await this.resolveBlockRefs(rawResults); const searchDescription = `referencing "${primary_tag}"`; const formattedResults = SearchUtils.formatSearchResults(resolvedResults, searchDescription, !targetPageUid); formattedResults.total_count = totalCount; return formattedResults; } }