011ccfb8cd
Plugin fino (id osint-db) que habla HTTP con el service local osint_db (FastAPI + DuckDB) y renderiza tablas de datos en las notas del vault osint mediante el code block osintdb. Incluye parser puro de directivas con tests (node --test), settings tab, comando de paleta, enlaces internos para columnas note_path, build con esbuild + tsc y deploy.sh al vault. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
// 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: <nombre>'." };
|
|
}
|
|
|
|
result.sql = rest;
|
|
return result;
|
|
}
|