Die Dokumentation wurde gelesen, man weiß, dass TOON die Token-Anzahl bei tabellarischen Daten halbiert. Jetzt möchte man es wirklich in etwas einbauen. Dieser Artikel handelt von der Verkabelung: .toon-Dateien lesen und schreiben, TOON an Systemgrenzen validieren, eine Express-Middleware erstellen, die TOON-Anfrage-Bodies parst, und eine Datenbank-zu-Prompt-Pipeline zusammenstellen, die TOON direkt an ein LLM übergibt. Echter Code, echte Muster — keine Spielzeugbeispiele.

Einrichtung

Das Paket von npm installieren. Es ist nur ESM, also braucht man "type": "module" in der package.json oder eine .mjs-Erweiterung. Node.js 18+ ist alles, was benötigt wird — keine Konfigurationsdateien, keine Plugins.

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

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

TOON-Dateien lesen und schreiben

Das Node.js fs-Modul übernimmt die Ein/Ausgabe. Den Dateiinhalt direkt an decode() übergeben, oder die Daten an encode() übergeben und das Ergebnis auf Disk schreiben. Unten sind beide Muster — synchron für Skripte und CLI-Tools, asynchron für Server-Routen.

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');
}
Immer 'utf8'-Kodierung verwenden. TOON-Dateien sind Klartext. Das Weglassen des Kodierungsarguments gibt einen Buffer zurück — decode() erwartet einen String und wirft einen Typfehler, wenn ein Buffer übergeben wird.

TOON an Systemgrenzen validieren

decode() wirft bei ungültiger Eingabe, was das richtige Verhalten für einen Parser ist, aber bei einer API-Grenze oder einem Message-Queue-Consumer unpraktisch ist, wo man ein strukturiertes Ergebnis benötigt, keine unbehandelte Exception. Die Lösung ist ein dünner Wrapper, der das Throw in einen Rückgabewert umwandelt. Das ist das Muster, das man in Express-Route-Handlern, Queue-Prozessoren und überall sonst verwendet, wo externe Daten in das System eintreten.

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

Die Verwendung in einer Express-Route oder einem Queue-Consumer sieht identisch aus — validateToon() aufrufen, auf valid verzweigen, und entweder mit data fortfahren oder eine 400-Antwort zurückgeben / die Nachricht mit dem error-String in die Dead-Letter-Queue verschieben. Das try/catch-Muster hält den aufrufenden Code sauber und vorhersehbar.

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

Eine TOON-Middleware für Express erstellen

express.json() parst application/json-Bodies und legt das Ergebnis auf req.body. Hier ist dasselbe für application/toon. Vor den Route-Handlern einbauen, und der Rest des Stacks merkt den Unterschied nicht.

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

Datenbankergebnisse in TOON konvertieren, bevor sie an ein LLM gesendet werden

Das ist das Muster, für das TOON gebaut wurde. Die Datenbank abfragen, ein Array von Zeilen zurückbekommen, zu TOON kodieren und direkt in den Prompt einfügen. Das LLM bekommt die gesamte Struktur ohne den Schlüsselwiederholungs-Overhead von JSON. Hier ist eine realistische Pipeline mit 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);
}

Dasselbe Muster funktioniert für jeden SQL-Client oder ORM — Prisma, Drizzle, Knex, Sequelize — solange die Abfrage einfache JS-Objekte zurückgibt. encode() nimmt die Schlüsselnamen aus der ersten Zeile und verwendet sie als Spaltenheader; nachfolgende Zeilen werden als durch Kommas getrennte Werte geschrieben. Ein 50-Zeilen-Ergebnis, das als JSON-Array ~1.500 Tokens kosten würde, kostet als TOON typischerweise ~600–700 Tokens.

Fehler und Grenzfälle behandeln

Ein paar Dinge, die man vor dem Deployment wissen sollte:

  • LLM gibt fehlerhaftes TOON zurück. Modelle reproduzieren ein Format nicht immer perfekt, besonders beim ersten Versuch. decode() in ein try/catch einwickeln (oder validateToon() von oben verwenden). Wenn es fehlschlägt, die Rohantwort protokollieren, einen Fehler an den Aufrufer zurückgeben, und — wenn zuverlässig strukturierte Ausgaben benötigt werden — einen Retry mit einem expliziten Korrektur-Prompt hinzufügen: "Deine letzte Antwort war kein gültiges TOON. Bitte formatiere es neu."
  • Werte mit Kommas oder Doppelpunkten. TOON verwendet Kommas zum Trennen von Werten und Doppelpunkte in der Objektsyntax — beides sind signifikante Zeichen. encode() erkennt diese automatisch und setzt den betroffenen Wert in doppelte Anführungszeichen. Die Daten müssen nie vorverarbeitet werden; einfach rohe Strings übergeben.
  • Null und undefined. encode() serialisiert null als null (nackt, ohne Anführungszeichen) und lässt undefined-Eigenschaften vollständig weg — dasselbe Verhalten wie JSON.stringify(). Beim Dekodieren wird nacktes null als JS null zurückgegeben.
  • Leere Arrays. encode([]) gibt ein gültiges leeres TOON-Array zurück. decode() behandelt es sauber. Upstream schützen, wenn der LLM-Prompt keinen leeren Datensatz enthalten soll.
  • Sehr große Ergebnismengen. In der Bibliothek gibt es kein hartes Limit, aber LLMs haben Kontextfensterlimits. Abfragen paginieren oder mit LIMIT begrenzen, bevor kodiert wird — 100–200 Zeilen ist eine vernünftige Obergrenze für die meisten Prompts.
Vor dem Speichern validieren. Wenn die Pipeline TOON von einer externen Quelle (Webhook, Queue, API-Client) akzeptiert und das dekodierte Ergebnis in einer Datenbank speichert, immer zuerst validateToon() ausführen. Einen fehlerhaften Payload bis zur DB-Schicht gelangen zu lassen, macht das Debugging viel schwieriger als ihn an der Grenze abzufangen.

Zusammenfassung

Die Muster in diesem Artikel decken das meiste ab, was benötigt wird, um TOON in eine echte Node.js-Codebasis zu integrieren: fs für Datei-Ein/Ausgabe, ein validateToon()-Wrapper für sicheres Boundary-Parsing, eine Drop-in-Express-Middleware für application/toon-Bodies und eine DB-zu-Prompt-Pipeline, die SQL-Zeilen in token-effiziente LLM-Eingaben umwandelt. Die Bibliothek selbst — @toon-format/toon — bleibt aus dem Weg: zwei Funktionen, keine Konfiguration, wirft bei ungültiger Eingabe. Den TOON Validator verwenden, um Ausgaben während der Entwicklung zu prüfen, den TOON Formatter, um kodierte Daten zu inspizieren, JSON zu TOON, um bestehende Datensätze zu konvertieren, bevor sie in einen Prompt eingefügt werden, und TOON zu JSON, wenn eine dekodierte Antwort an ein Downstream-System übergeben werden muss, das JSON erwartet.