Vous avez lu la documentation, vous savez que TOON réduit de moitié les comptages de tokens sur les données tabulaires. Maintenant vous voulez réellement le câbler dans quelque chose. Cet article parle de la plomberie : lire et écrire des fichiers .toon, valider TOON aux limites du système, créer un middleware Express qui parse les corps de requêtes TOON, et assembler un pipeline base de données-vers-prompt qui alimente TOON directement à un LLM. Du code réel, des patterns réels — pas d'exemples jouets.
Configuration
Installez le paquet depuis npm. C'est ESM uniquement, vous aurez donc besoin de "type": "module" dans votre package.json ou d'utiliser une extension .mjs. Node.js 18+ est tout ce dont vous avez besoin — pas de fichiers de configuration, pas de plugins.
npm install @toon-format/toonimport { encode, decode } from '@toon-format/toon';
// That's it. encode() → TOON string, decode() → JS value.Lire et Écrire des Fichiers TOON
Le module fs de Node.js gère les E/S. Passez le contenu du fichier directement dans decode(), ou passez vos données à encode() et écrivez le résultat sur le disque. Voici les deux patterns — sync pour les scripts et les outils CLI, async pour les routes de serveur.
// --- 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'. Les fichiers TOON sont du texte brut. Omettre l'argument d'encodage retourne un Buffer — decode() attend une chaîne et lancera une erreur de type si on lui passe un Buffer.Valider TOON aux Limites du Système
decode() lance une exception sur une entrée invalide, ce qui est le bon comportement pour un parser mais peu pratique à une limite d'API ou un consommateur de file de messages où vous avez besoin d'un résultat structuré, pas d'une exception non interceptée. La solution est une fine couche d'encapsulation qui transforme le lancer en valeur de retour. C'est le pattern que vous utiliserez dans les gestionnaires de routes Express, les processeurs de file d'attente, et partout ailleurs où des données externes entrent dans votre système.
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'utilisation dans une route Express ou un consommateur de file d'attente est identique — appelez validateToon(), branchez sur valid, et soit continuez avec data, soit retournez un 400 / mettez le message en file morte avec la chaîne error. Le pattern try/catch garde le code appelant propre et prévisible.
// 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);
});
Créer un Middleware TOON pour Express
express.json() parse les corps application/json et met le résultat sur req.body. Voici la même chose pour application/toon. Mettez-le avant vos gestionnaires de routes et le reste de la stack ne verra jamais la différence.
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 les Résultats de Base de Données en TOON Avant d'Envoyer à un LLM
C'est le pattern pour lequel TOON a été construit. Vous interrogez la base de données, récupérez un tableau de lignes, encodez en TOON, et l'intégrez directement dans votre prompt. Le LLM obtient toute la structure sans le surcoût de répétition de clés de JSON. Voici un pipeline réaliste utilisant 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);
}Le même pattern fonctionne pour tout client SQL ou ORM — Prisma, Drizzle, Knex, Sequelize — tant que votre requête retourne des objets JS simples. encode() récupère les noms de clés de la première ligne et les utilise comme en-têtes de colonnes ; les lignes suivantes sont écrites comme des valeurs séparées par des virgules. Un ensemble de résultats de 50 lignes qui coûterait ~1 500 tokens comme un tableau JSON coûte généralement ~600-700 tokens en TOON.
Gestion des Erreurs et des Cas Limites
Quelques points à savoir avant de déployer :
- Le LLM retourne du TOON malformé. Les modèles ne reproduisent pas toujours un format parfaitement, surtout à la première tentative. Encapsulez
decode()dans un try/catch (ou utilisezvalidateToon()ci-dessus). Si ça échoue, loggez la réponse brute, retournez une erreur à l'appelant, et — si vous avez besoin d'une sortie structurée de manière fiable — ajoutez une nouvelle tentative avec un prompt de correction explicite : "Votre dernière réponse n'était pas du TOON valide. Veuillez la reformater." - Valeurs contenant des virgules ou des deux-points. TOON utilise des virgules pour séparer les valeurs et des deux-points dans la syntaxe d'objet — les deux sont des caractères significatifs.
encode()les détecte automatiquement et encadre la valeur concernée entre guillemets doubles. Vous n'avez jamais besoin de pré-traiter vos données ; passez simplement des chaînes brutes. - Null et undefined.
encode()sérialisenullcommenull(nu, sans guillemets) et omet complètement les propriétésundefined— le même comportement queJSON.stringify(). Lors du décodage, lenullnu est retourné commenullJS. - Tableaux vides.
encode([])retourne un tableau TOON vide valide.decode()le fait passer en boucle proprement. Protégez en amont si votre prompt LLM ne devrait pas inclure un dataset vide. - Très grands ensembles de résultats. Il n'y a pas de limite stricte dans la bibliothèque, mais les LLMs ont des limites de fenêtre de contexte. Paginez ou
LIMITez vos requêtes avant l'encodage — 100-200 lignes est un plafond raisonnable pour la plupart des prompts.
validateToon() en premier. Laisser une charge utile malformée atteindre votre couche DB rend le débogage bien plus difficile que de l'intercepter à la limite.En Conclusion
Les patterns dans cet article couvrent la plupart de ce dont vous avez besoin pour intégrer TOON dans une vraie base de code Node.js : fs pour les E/S de fichiers, une couche validateToon() pour le parsing sécurisé aux limites, un middleware Express drop-in pour les corps application/toon, et un pipeline DB-vers-prompt qui transforme les lignes SQL en entrée LLM efficace en tokens. La bibliothèque elle-même — @toon-format/toon — reste à l'écart : deux fonctions, pas de configuration, lance une exception sur une entrée invalide. Utilisez le Validateur TOON pour vérifier les sorties pendant le développement, le Formateur TOON pour inspecter les données encodées, JSON vers TOON pour convertir les datasets existants avant de les coller dans un prompt, et TOON vers JSON si vous devez passer une réponse décodée à un système en aval qui attend du JSON.