Ya has leído la documentación, sabes que TOON reduce a la mitad los recuentos de tokens en datos tabulares. Ahora quieres conectarlo realmente a algo. Este artículo trata sobre la plomería: leer y escribir archivos .toon, validar TOON en los límites del sistema, construir un middleware de Express que analice cuerpos de solicitudes TOON, y ensamblar un pipeline de base de datos a prompt que alimenta TOON directamente a un LLM. Código real, patrones reales — sin ejemplos de juguete.

Configuración

Instala el paquete desde npm. Es solo ESM, por lo que necesitarás "type": "module" en tu package.json o usar una extensión .mjs. Node.js 18+ es todo lo que necesitas — sin archivos de configuración, sin plugins.

bash
npm install @toon-format/toon
js
import { encode, decode } from '@toon-format/toon';

// That's it. encode() → TOON string, decode() → JS value.

Leer y Escribir Archivos TOON

El módulo fs de Node.js maneja la E/S. Pasa el contenido del archivo directamente a decode(), o pasa tus datos a encode() y escribe el resultado en disco. A continuación se muestran ambos patrones — sync para scripts y herramientas CLI, async para rutas de servidor.

js
// --- Sync (scripts, CLI tools) ---
import { readFileSync, writeFileSync } from 'fs';
import { encode, decode } from '@toon-format/toon';

// Read a .toon file and decode it to a JS value
const raw = readFileSync('./data/products.toon', 'utf8');
const products = decode(raw);
console.log(products); // → JS array or object

// Encode a JS value and write it to a .toon file
const inventory = [
  { sku: 'WDG-001', name: 'Widget A', qty: 142, price: 9.99 },
  { sku: 'WDG-002', name: 'Widget B', qty: 87,  price: 14.49 },
  { sku: 'GDG-001', name: 'Gadget X', qty: 31,  price: 49.99 },
];
writeFileSync('./data/inventory.toon', encode(inventory, { indent: 2 }), 'utf8');
js
// --- Async (server routes, pipelines) ---
import { promises as fs } from 'fs';
import { encode, decode } from '@toon-format/toon';

// Read
async function loadReportData(filePath) {
  const raw = await fs.readFile(filePath, 'utf8');
  return decode(raw); // throws if malformed — handle upstream
}

// Write
async function saveSnapshot(data, filePath) {
  const toon = encode(data, { indent: 2 });
  await fs.writeFile(filePath, toon, 'utf8');
}
Usa siempre la codificación 'utf8'. Los archivos TOON son texto plano. Omitir el argumento de codificación devuelve un Buffer — decode() espera una cadena y lanzará un error de tipo si se le pasa un Buffer.

Validar TOON en los Límites del Sistema

decode() lanza una excepción con entradas inválidas, lo cual es el comportamiento correcto para un parser pero inconveniente en un límite de API o un consumidor de cola de mensajes donde necesitas un resultado estructurado, no una excepción no capturada. La solución es una capa delgada que convierte el lanzamiento en un valor de retorno. Este es el patrón que usarás en los manejadores de rutas de Express, los procesadores de cola, y en cualquier otro lugar donde datos externos entren a tu sistema.

js
import { decode } from '@toon-format/toon';

/**
 * Safely parse a TOON string.
 * Returns { valid: true, data } on success,
 * or { valid: false, error } on failure — never throws.
 */
export function validateToon(input) {
  if (typeof input !== 'string') {
    return { valid: false, error: 'Input must be a string' };
  }
  try {
    const data = decode(input);
    return { valid: true, data };
  } catch (err) {
    return { valid: false, error: err.message };
  }
}

El uso en una ruta de Express o un consumidor de cola es idéntico — llama a validateToon(), ramifica en valid, y ya sea procede con data o devuelve un 400 / envía el mensaje a dead-letter con la cadena error. El patrón try/catch mantiene el código llamador limpio y predecible.

js
// Example: queue consumer
queue.process('ingest-toon', async (job) => {
  const result = validateToon(job.data.payload);
  if (!result.valid) {
    console.error('Rejecting malformed TOON:', result.error);
    return; // dead-letter, skip, or throw depending on your queue
  }
  await db.insert(result.data);
});

Construir un Middleware TOON para Express

express.json() analiza los cuerpos application/json y pone el resultado en req.body. Aquí está lo mismo para application/toon. Colócalo antes de tus manejadores de ruta y el resto del stack nunca notará la diferencia.

js
import { decode } from '@toon-format/toon';

/**
 * Express middleware: parses application/toon request bodies
 * and attaches the decoded value to req.body.
 */
export function toonBodyParser(req, res, next) {
  const contentType = req.headers['content-type'] ?? '';
  if (!contentType.includes('application/toon')) {
    return next(); // not our content type, pass through
  }

  let body = '';
  req.setEncoding('utf8');
  req.on('data', (chunk) => { body += chunk; });
  req.on('end', () => {
    try {
      req.body = decode(body);
      next();
    } catch (err) {
      res.status(400).json({ error: 'Invalid TOON body', detail: err.message });
    }
  });
  req.on('error', (err) => {
    res.status(500).json({ error: 'Request stream error', detail: err.message });
  });
}

// Wire it up:
// app.use(toonBodyParser);
// app.post('/api/import', (req, res) => {
//   // req.body is already the decoded JS value
//   res.json({ received: Array.isArray(req.body) ? req.body.length : 1 });
// });

Convertir Resultados de Base de Datos a TOON Antes de Enviar al LLM

Este es el patrón para el que fue construido TOON. Consultas la base de datos, obtienes un array de filas, codificas a TOON, y lo insertas directamente en tu prompt. El LLM obtiene toda la estructura sin la sobrecarga de repetición de claves de JSON. Aquí hay un pipeline realista usando node-postgres (pg):

js
import pg from 'pg';
import { encode } from '@toon-format/toon';

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });

async function buildOrderPrompt(customerId) {
  // Step 1: query the database
  const { rows } = await pool.query(
    `SELECT order_id, created_at, status, total_cents, item_count
       FROM orders
      WHERE customer_id = $1
      ORDER BY created_at DESC
      LIMIT 50`,
    [customerId]
  );

  if (rows.length === 0) {
    return null;
  }

  // Step 2: encode rows to TOON
  // encode() handles all quoting automatically — no pre-processing needed
  const toonData = encode(rows, { indent: 2 });

  // Step 3: build the prompt
  return [
    'Analyse the following order history for a customer support case.',
    'Data is in TOON tabular format: name[count]{col1,col2,...}: followed by one row per line.',
    '',
    toonData,
    '',
    'Summarise any patterns that suggest the customer has a recurring issue.'
  ].join('\n');
}

// Calling code:
const prompt = await buildOrderPrompt('cust_8821');
if (prompt) {
  const reply = await callLlm(prompt); // your LLM client here
  console.log(reply);
}

El mismo patrón funciona para cualquier cliente SQL u ORM — Prisma, Drizzle, Knex, Sequelize — siempre que tu consulta devuelva objetos JS simples. encode() toma los nombres de clave de la primera fila y los usa como encabezados de columnas; las filas siguientes se escriben como valores separados por comas. Un conjunto de resultados de 50 filas que costaría ~1,500 tokens como un array JSON típicamente cuesta ~600-700 tokens como TOON.

Manejo de Errores y Casos Extremos

Algunas cosas que vale la pena saber antes de desplegar:

  • El LLM devuelve TOON malformado. Los modelos no siempre reproducen un formato perfectamente, especialmente en el primer intento. Envuelve decode() en un try/catch (o usa validateToon() de arriba). Si falla, registra la respuesta bruta, devuelve un error al llamador, y — si necesitas salida estructurada de manera confiable — agrega un reintento con un prompt de corrección explícito: "Tu última respuesta no era TOON válido. Por favor reformateala."
  • Valores que contienen comas o dos puntos. TOON usa comas para separar valores y dos puntos en la sintaxis de objetos — ambos son caracteres significativos. encode() los detecta automáticamente y envuelve el valor afectado entre comillas dobles. Nunca necesitas pre-procesar tus datos; simplemente pasa cadenas sin procesar.
  • Null y undefined. encode() serializa null como null (desnudo, sin comillas) y omite completamente las propiedades undefined — el mismo comportamiento que JSON.stringify(). Al decodificar, el null desnudo se devuelve como null de JS.
  • Arrays vacíos. encode([]) devuelve un array TOON vacío válido. decode() lo hace circular limpiamente. Protege en upstream si tu prompt de LLM no debería incluir un dataset vacío.
  • Conjuntos de resultados muy grandes. No hay un límite estricto en la biblioteca, pero los LLMs tienen límites de ventana de contexto. Pagina o usa LIMIT en tus consultas antes de codificar — 100-200 filas es un techo razonable para la mayoría de los prompts.
Valida antes de almacenar. Si tu pipeline acepta TOON de una fuente externa (webhook, cola, cliente API) y almacena el resultado decodificado en una base de datos, siempre ejecuta validateToon() primero. Dejar que una carga útil malformada llegue a tu capa de DB hace que la depuración sea mucho más difícil que detectarla en el límite.

En Resumen

Los patrones en este artículo cubren la mayor parte de lo que necesitas para integrar TOON en una base de código Node.js real: fs para E/S de archivos, una capa validateToon() para el parsing seguro en límites, un middleware Express plug-and-play para cuerpos application/toon, y un pipeline DB-a-prompt que convierte filas SQL en entrada LLM eficiente en tokens. La biblioteca en sí — @toon-format/toon — se mantiene al margen: dos funciones, sin configuración, lanza excepciones con entradas inválidas. Usa el Validador TOON para verificar salidas durante el desarrollo, el Formateador TOON para inspeccionar datos codificados, JSON a TOON para convertir datasets existentes antes de pegarlos en un prompt, y TOON a JSON si necesitas entregar una respuesta decodificada a un sistema downstream que espera JSON.