// Parseo del contenido de un bloque ```osintdb```. Módulo puro, sin dependencias // de Obsidian, para poder testearlo con node --test sin levantar la app. // // Dos formas de bloque: // 1. SQL crudo: el bloque entero es una sentencia SELECT que se manda a /api/query. // 2. Con directivas: líneas "clave: valor" al inicio del bloque. Si hay directiva // "query" se ejecuta una query nombrada via /api/query/named. Las directivas // "max_rows" y "title" aplican a ambas formas. export interface ParsedBlock { /** Nombre de la query guardada en el service (forma named). */ query?: string; /** SQL crudo a ejecutar (forma raw). */ sql?: string; /** Límite de filas pedido al service. */ maxRows?: number; /** Título a mostrar encima de la tabla. */ title?: string; /** Mensaje de error de parseo; si está presente, el resto se ignora. */ error?: string; } const DIRECTIVE_RE = /^(query|max_rows|title)\s*:\s*(.*)$/; /** * Parsea el texto de un bloque osintdb. Consume las líneas de directivas del * inicio (clave: valor) y trata el resto como SQL crudo. Reglas: * - "query" presente -> query nombrada (no se admite SQL adicional en el bloque). * - sin "query" pero con SQL -> query cruda. * - bloque vacío o directivas inválidas -> error. */ export function parseBlock(source: string): ParsedBlock { const lines = source.split(/\r?\n/); const result: ParsedBlock = {}; let i = 0; for (; i < lines.length; i++) { const line = lines[i].trim(); if (line === "" && result.query === undefined && result.maxRows === undefined && result.title === undefined) { // Líneas en blanco iniciales antes de cualquier directiva: se saltan. continue; } const m = line.match(DIRECTIVE_RE); if (!m) { break; // Fin de las directivas; lo que queda es SQL. } const key = m[1]; const value = m[2].trim(); if (key === "query") { if (value === "") { return { error: "La directiva 'query' necesita un nombre de query guardada." }; } result.query = value; } else if (key === "max_rows") { const n = Number(value); if (!Number.isInteger(n) || n <= 0) { return { error: `Valor inválido para max_rows: "${value}" (debe ser un entero positivo).` }; } result.maxRows = n; } else if (key === "title") { result.title = value; } } const rest = lines.slice(i).join("\n").trim(); if (result.query !== undefined) { if (rest !== "") { return { error: "El bloque mezcla la directiva 'query' con SQL. Usa una de las dos formas." }; } return result; } if (rest === "") { return { error: "Bloque vacío: escribe un SELECT o una directiva 'query: '." }; } result.sql = rest; return result; }