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.
npm install @toon-format/toonimport { 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.
// --- 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');
// --- 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');
}'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.
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.
// 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.
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):
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 usavalidateToon()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()serializzanullcomenull(grezzo, non quotato) e omette completamente le proprietàundefined— lo stesso comportamento diJSON.stringify(). Durante la decodifica, ilnullgrezzo viene restituito come JSnull. - 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.
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.