UNPKG

roam-research-mcp

Version:
102 lines (101 loc) • 4.49 kB
import { q } from '@roam-research/roam-api-sdk'; import { BaseSearchHandler } from './types.js'; import { SearchUtils } from './utils.js'; import { ANCESTOR_RULE } from './ancestor-rule.js'; import { resolveRefs } from '../tools/helpers/refs.js'; export class HierarchySearchHandler extends BaseSearchHandler { constructor(graph, params) { super(graph); this.params = params; } async execute() { const { parent_uid, child_uid, page_title_uid, max_depth = 1 } = this.params; if (!parent_uid && !child_uid) { return { success: false, matches: [], message: 'Either parent_uid or child_uid must be provided' }; } // Get target page UID if provided let targetPageUid; if (page_title_uid) { targetPageUid = await SearchUtils.findPageByTitleOrUid(this.graph, page_title_uid); } let queryStr; let queryParams; if (parent_uid) { // Search for all descendants using ancestor rule if (targetPageUid) { queryStr = `[:find ?block-uid ?block-str ?depth :in $ % ?parent-uid ?page-uid :where [?p :block/uid ?page-uid] [?parent :block/uid ?parent-uid] (ancestor ?b ?parent) [?b :block/string ?block-str] [?b :block/uid ?block-uid] [?b :block/page ?p] [(get-else $ ?b :block/path-length 1) ?depth]]`; queryParams = [ANCESTOR_RULE, parent_uid, targetPageUid]; } else { queryStr = `[:find ?block-uid ?block-str ?page-title ?depth :in $ % ?parent-uid :where [?parent :block/uid ?parent-uid] (ancestor ?b ?parent) [?b :block/string ?block-str] [?b :block/uid ?block-uid] [?b :block/page ?p] [?p :node/title ?page-title] [(get-else $ ?b :block/path-length 1) ?depth]]`; queryParams = [ANCESTOR_RULE, parent_uid]; } } else { // Search for ancestors using the same rule if (targetPageUid) { queryStr = `[:find ?block-uid ?block-str ?depth :in $ % ?child-uid ?page-uid :where [?p :block/uid ?page-uid] [?child :block/uid ?child-uid] (ancestor ?child ?b) [?b :block/string ?block-str] [?b :block/uid ?block-uid] [?b :block/page ?p] [(get-else $ ?b :block/path-length 1) ?depth]]`; queryParams = [ANCESTOR_RULE, child_uid, targetPageUid]; } else { queryStr = `[:find ?block-uid ?block-str ?page-title ?depth :in $ % ?child-uid :where [?child :block/uid ?child-uid] (ancestor ?child ?b) [?b :block/string ?block-str] [?b :block/uid ?block-uid] [?b :block/page ?p] [?p :node/title ?page-title] [(get-else $ ?b :block/path-length 1) ?depth]]`; queryParams = [ANCESTOR_RULE, child_uid]; } } const rawResults = await q(this.graph, queryStr, queryParams); // Resolve block references and format results to include depth information const matches = await Promise.all(rawResults.map(async ([uid, content, pageTitle, depth]) => { const resolvedContent = await resolveRefs(this.graph, content); return { block_uid: uid, content: resolvedContent, depth: depth || 1, ...(pageTitle && { page_title: pageTitle }) }; })); const searchDescription = parent_uid ? `descendants of block ${parent_uid}` : `ancestors of block ${child_uid}`; return { success: true, matches, message: `Found ${matches.length} block(s) as ${searchDescription}` }; } }