Hai letto la documentazione, sai che TOON dimezza i conteggi di token sui dati tabulari. Ora vuoi effettivamente collegarlo a qualcosa. Questo articolo riguarda l'idraulica: lettura e scrittura di file .toon, validazione di TOON ai confini del sistema, costruzione di un middleware Express che analizza i body delle richieste TOON, e assemblaggio di una pipeline database-to-prompt che alimenta TOON direttamente a un LLM. Codice reale, pattern reali — nessun esempio giocattolo.

Configurazione

Installa il pacchetto da npm. È solo ESM, quindi avrai bisogno di "type": "module" nel tuo package.json o usa un'estensione .mjs. Node.js 18+ è tutto ciò di cui hai bisogno — nessun file di configurazione, nessun plugin.

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

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

Lettura e scrittura di file TOON

Il modulo fs di Node.js gestisce l'I/O. Passa i contenuti del file direttamente a decode(), o passa i tuoi dati a encode() e scrivi il risultato su disco. Di seguito sono riportati entrambi i pattern — sync per script e strumenti CLI, async per le route del server.

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 sempre la codifica 'utf8'. I file TOON sono testo semplice. Omettere l'argomento di codifica restituisce un Buffer — decode() si aspetta una stringa e genererà un errore di tipo se gli viene passato un Buffer.

Validazione di TOON ai confini del sistema

decode() genera un'eccezione su input non valido, il che è il comportamento corretto per un parser ma scomodo a un confine API o a un consumer di code di messaggi dove hai bisogno di un risultato strutturato, non di un'eccezione non catturata. La soluzione è un sottile wrapper che trasforma l'eccezione in un valore di ritorno. Questo è il pattern a cui si ricorre nei gestori di route Express, nei processori di code e ovunque i dati esterni entrino nel 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 };
  }
}

L'utilizzo in una route Express o in un consumer di code sembra identico — chiama validateToon(), ramifica su valid, e procedi con data o restituisci un 400 / invia nella dead-letter il messaggio con la stringa error. Il pattern try/catch mantiene il codice chiamante pulito e prevedibile.

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);
});

Costruzione di un middleware TOON per Express

express.json() analizza i body application/json e mette il risultato su req.body. Ecco la stessa cosa per application/toon. Inseriscilo prima dei gestori di route e il resto dello stack non sa mai la differenza.

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 });
// });

Conversione dei risultati del database in TOON prima dell'invio all'LLM

Questo è il pattern per cui TOON è stato costruito. Esegui una query nel database, ottieni un array di righe, codifica in TOON e inseriscilo direttamente nel prompt. L'LLM ottiene tutta la struttura senza l'overhead di ripetizione delle chiavi di JSON. Ecco una pipeline realistica che usa 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);
}

Lo stesso pattern funziona per qualsiasi client SQL o ORM — Prisma, Drizzle, Knex, Sequelize — purché la query restituisca semplici oggetti JS. encode() prende i nomi delle chiavi dalla prima riga e li usa come intestazioni di colonna; le righe successive vengono scritte come valori separati da virgole. Un risultato di 50 righe che costerebbe ~1.500 token come array JSON tipicamente costa ~600–700 token come TOON.

Gestione degli errori e casi limite

Alcune cose da sapere prima di mettere in produzione:

  • L'LLM restituisce TOON malformato. I modelli non riproducono sempre perfettamente un formato, specialmente al primo tentativo. Avvolgi decode() in un try/catch (o usa validateToon() da sopra). Se fallisce, registra la risposta grezza, restituisci un errore al chiamante, e — se hai bisogno di output strutturato in modo affidabile — aggiungi un retry con un prompt di correzione esplicito: "La tua ultima risposta non era TOON valido. Si prega di riformattarla."
  • Valori contenenti virgole o due punti. TOON usa le virgole per separare i valori e i due punti nella sintassi degli oggetti — entrambi sono caratteri significativi. encode() li rileva automaticamente e avvolge il valore interessato tra virgolette doppie. Non è necessario pre-elaborare i dati; basta passare stringhe grezze.
  • Null e undefined. encode() serializza null come null (grezzo, non quotato) e omette completamente le proprietà undefined — lo stesso comportamento di JSON.stringify(). Durante la decodifica, il null grezzo viene restituito come JS null.
  • Array vuoti. encode([]) restituisce un array TOON vuoto valido. decode() lo gestisce in modo pulito. Proteggi a monte se il tuo prompt LLM non dovrebbe includere un dataset vuoto.
  • Set di risultati molto grandi. Non c'è un limite rigido nella libreria, ma gli LLM hanno limiti di finestre di contesto. Pagina o limita le query prima di codificare — 100–200 righe è un tetto ragionevole per la maggior parte dei prompt.
Valida prima di archiviare. Se la tua pipeline accetta TOON da una fonte esterna (webhook, coda, client API) e archivia il risultato decodificato in un database, esegui sempre validateToon() prima. Lasciare che un payload malformato raggiunga il livello DB rende il debug molto più difficile che catturarlo al confine.

Conclusioni

I pattern in questo articolo coprono la maggior parte di ciò che hai bisogno per integrare TOON in un reale codebase Node.js: fs per l'I/O dei file, un wrapper validateToon() per l'analisi sicura ai confini, un middleware Express drop-in per i body application/toon, e una pipeline DB-to-prompt che trasforma le righe SQL in input LLM efficiente in termini di token. La libreria stessa — @toon-format/toon — si fa da parte: due funzioni, nessuna configurazione, genera un'eccezione su input non valido. Usa il Validatore TOON per controllare gli output durante lo sviluppo, il Formattatore TOON per ispezionare i dati codificati, il convertitore da JSON a TOON per convertire i dataset esistenti prima di incollarli in un prompt, e il convertitore da TOON a JSON se hai bisogno di passare una risposta decodificata a un sistema downstream che si aspetta JSON.