Você leu a documentação, sabe que o TOON corta as contagens de tokens pela metade em dados tabulares. Agora você quer realmente conectar isso em algo. Este artigo é sobre o encanamento: ler e escrever arquivos .toon, validar TOON em fronteiras de sistema, construir um middleware Express que analisa corpos de requisição TOON, e montar um pipeline banco-de-dados-para-prompt que alimenta TOON diretamente para um LLM. Código real, padrões reais — sem exemplos brinquedo.
Configuração
Instale o pacote do npm. É apenas ESM, então você vai precisar de "type": "module" no seu package.json ou usar a extensão .mjs. Node.js 18+ é tudo que você precisa — sem arquivos de configuração, sem plugins.
npm install @toon-format/toonimport { encode, decode } from '@toon-format/toon';
// That's it. encode() → TOON string, decode() → JS value.Lendo e escrevendo arquivos TOON
O módulo fs do Node.js lida com o I/O. Passe o conteúdo do arquivo diretamente para decode(), ou passe seus dados para encode() e escreva o resultado em disco. Abaixo estão ambos os padrões — síncronos para scripts e ferramentas CLI, assíncronos para rotas de servidor.
// --- 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'. Arquivos TOON são texto simples. Omitir o argumento de codificação retorna um Buffer — decode() espera uma string e lançará um erro de tipo se passado um Buffer.Validando TOON em fronteiras de sistema
decode() lança em entrada inválida, o que é o comportamento certo para um parser mas inconveniente em uma fronteira de API ou consumidor de fila de mensagens onde você precisa de um resultado estruturado, não uma exceção não capturada. A solução é um wrapper fino que transforma o lançamento em um valor de retorno. Este é o padrão que você vai usar em Express route handlers, processadores de fila, e em qualquer outro lugar onde dados externos entram no seu 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 };
}
}
O uso em uma rota Express ou um consumidor de fila parece idêntico — chame validateToon(), ramifique em valid, e ou prossiga com data ou retorne um 400 / dead-letter a mensagem com a string error. O padrão try/catch mantém o código chamador limpo e previsível.
// 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);
});
Construindo um middleware TOON para Express
express.json() analisa corpos application/json e coloca o resultado em req.body. Aqui está a mesma coisa para application/toon. Coloque antes dos seus route handlers e o resto da pilha nunca saberá a diferença.
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 });
// });Convertendo resultados de banco de dados para TOON antes de enviar para LLM
Este é o padrão para o qual o TOON foi construído. Você consulta o banco de dados, recebe de volta um array de linhas, codifica para TOON e coloca direto no seu prompt. O LLM obtém toda a estrutura sem o overhead de repetição de chaves do JSON. Aqui está um pipeline realista usando 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);
}O mesmo padrão funciona para qualquer cliente SQL ou ORM — Prisma, Drizzle, Knex, Sequelize — desde que sua consulta retorne objetos JS simples. encode() pega os nomes das chaves da primeira linha e os usa como cabeçalhos de coluna; linhas subsequentes são escritas como valores separados por vírgulas. Um resultado de 50 linhas que custaria ~1.500 tokens como um array JSON tipicamente custa ~600–700 tokens como TOON.
Lidando com erros e casos extremos
Algumas coisas vale a pena saber antes de você enviar:
- LLM retorna TOON malformado. Os modelos nem sempre reproduzem um formato perfeitamente, especialmente na primeira tentativa. Envolva
decode()em um try/catch (ou usevalidateToon()acima). Se falhar, registre a resposta bruta, retorne um erro para o chamador, e — se precisar de saída estruturada de forma confiável — adicione uma tentativa de reprocessamento com um prompt de correção explícito: "Sua última resposta não era TOON válido. Por favor, reformate." - Valores contendo vírgulas ou dois-pontos. TOON usa vírgulas para separar valores e dois-pontos na sintaxe de objeto — ambos são caracteres significativos.
encode()detecta esses automaticamente e envolve o valor afetado em aspas duplas. Você nunca precisa pré-processar seus dados; apenas passe strings brutas. - Null e undefined.
encode()serializanullcomonull(puro, sem aspas) e omite propriedadesundefinedcompletamente — mesmo comportamento queJSON.stringify(). Ao decodificar,nullpuro é retornado como JSnull. - Arrays vazios.
encode([])retorna um array TOON vazio válido.decode()faz o round-trip limpo. Guarde upstream se seu prompt de LLM não deve incluir um dataset vazio. - Conjuntos de resultados muito grandes. Não há limite rígido na biblioteca, mas LLMs têm limites de janela de contexto. Pagine ou
LIMITsuas consultas antes de codificar — 100–200 linhas é um teto razoável para a maioria dos prompts.
validateToon() primeiro. Deixar um payload malformado chegar à sua camada de banco de dados torna a depuração muito mais difícil do que capturá-lo na fronteira.Conclusão
Os padrões neste artigo cobrem a maior parte do que você precisa para integrar TOON em uma base de código Node.js real: fs para I/O de arquivo, um wrapper validateToon() para parsing seguro em fronteiras, um middleware Express drop-in para corpos application/toon, e um pipeline DB-para-prompt que transforma linhas SQL em entrada de LLM eficiente em tokens. A biblioteca em si — @toon-format/toon — fica fora do seu caminho: duas funções, sem configuração, lança em entrada inválida. Use TOON Validator para verificar saídas durante o desenvolvimento, TOON Formatter para inspecionar dados codificados, JSON para TOON para converter datasets existentes antes de colá-los em um prompt, e TOON para JSON se precisar passar uma resposta decodificada para um sistema downstream que espera JSON.