Je hebt de documentatie gelezen, je weet dat TOON tokentellingen halveert op tabulaire data. Nu wil je het er echt in bedraden. Dit artikel gaat over de loodgieterij: .toon-bestanden lezen en schrijven, TOON valideren aan systeemgrenzen, een Express-middleware bouwen die TOON-aanvraagteksten parseert, en een database-naar-prompt pijplijn samenstellen die TOON direct aan een LLM doorvoert. Echte code, echte patronen — geen speelgoedvoorbeelden.

Instelling

Installeer het pakket via npm. Het is alleen ESM, dus je hebt "type": "module" nodig in je package.json of gebruik een .mjs-extensie. Node.js 18+ is alles wat je nodig hebt — geen configuratiebestanden, geen 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-bestanden lezen en schrijven

De Node.js fs-module verwerkt de I/O. Geef de bestandsinhoud direct door aan decode(), of geef je data door aan encode() en schrijf het resultaat naar schijf. Hieronder staan beide patronen — sync voor scripts en CLI-tools, async voor serverroutes.

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');
}
Gebruik altijd 'utf8'-codering. TOON-bestanden zijn platte tekst. Het weglaten van het coderingsargument retourneert een Buffer — decode() verwacht een string en zal een typefout gooien als er een Buffer aan wordt doorgegeven.

TOON valideren aan systeemgrenzen

decode() gooit bij ongeldige invoer, wat het juiste gedrag is voor een parser maar onhandig aan een API-grens of berichtenwachtrij-consumer waar je een gestructureerd resultaat nodig hebt, geen onverwachte uitzondering. De oplossing is een dunne omhulsel die de gooi omzet naar een retourwaarde. Dit is het patroon dat je gebruikt in Express-route-handlers, wachtrij-processors en overal waar externe data je systeem binnenkomt.

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

Gebruik in een Express-route of een wachtrij-consumer ziet er identiek uit — roep validateToon() aan, vertakt op valid, en ga ofwel door met data of retourneer een 400 / verstuur het bericht naar de dead-letter met de error-string. Het try/catch-patroon houdt de aanroepende code schoon en voorspelbaar.

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

Een TOON-middleware voor Express bouwen

express.json() parseert application/json-teksten en zet het resultaat op req.body. Hier is hetzelfde voor application/toon. Zet het vóór je route-handlers en de rest van de stack weet het verschil nooit.

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

Databaseresultaten converteren naar TOON vóór verzending naar LLM

Dit is het patroon waarvoor TOON is gebouwd. Je bevraagt de database, krijgt een array van rijen terug, codeert naar TOON en zet het direct in je prompt. Het LLM krijgt alle structuur zonder JSON's sleutelherhalingsoverhead. Hier is een realistische pijplijn die node-postgres (pg) gebruikt:

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

Hetzelfde patroon werkt voor elke SQL-client of ORM — Prisma, Drizzle, Knex, Sequelize — zolang je query gewone JS-objecten retourneert. encode() pakt de sleutelnamen op uit de eerste rij en gebruikt ze als kolomkoppen; volgende rijen worden geschreven als door komma's gescheiden waarden. Een resultatenset van 50 rijen die ~1.500 tokens als JSON-array zou kosten, kost typisch ~600–700 tokens als TOON.

Fouten en randgevallen afhandelen

Een paar dingen om te weten voordat je het in productie neemt:

  • LLM retourneert misvormde TOON. Modellen reproduceren een formaat niet altijd perfect, vooral bij de eerste poging. Omhul decode() in een try/catch (of gebruik validateToon() van hierboven). Als het mislukt, log de ruwe respons, retourneer een fout aan de aanroeper, en — als je betrouwbaar gestructureerde uitvoer nodig hebt — voeg een retry toe met een expliciete correctieprompt: "Je laatste respons was geen geldige TOON. Formatteer het alsjeblieft opnieuw."
  • Waarden die komma's of dubbele punten bevatten. TOON gebruikt komma's om waarden te scheiden en dubbele punten in objectsyntaxis — beide zijn significante tekens. encode() detecteert deze automatisch en omhult de getroffen waarde met dubbele aanhalingstekens. Je hoeft je data niet voor te verwerken; geef gewoon ruwe strings door.
  • Null en undefined. encode() serialiseert null als null (kaal, zonder aanhalingstekens) en laat undefined-eigenschappen volledig weg — hetzelfde gedrag als JSON.stringify(). Bij decoderen wordt kaal null geretourneerd als JS null.
  • Lege arrays. encode([]) retourneert een geldige lege TOON-array. decode() handelt dit netjes af. Bescherm stroomopwaarts als je LLM-prompt geen lege dataset moet bevatten.
  • Zeer grote resultatensets. Er is geen harde limiet in de bibliotheek, maar LLMs hebben contextvensterlimieten. Pagineer of beperk je queries voor codering — 100–200 rijen is een redelijk plafond voor de meeste prompts.
Valideer voor opslag. Als je pijplijn TOON accepteert van een externe bron (webhook, wachtrij, API-client) en het gedecodeerde resultaat opslaat in een database, voer dan altijd validateToon() eerst uit. Een misvormde payload laten doordringen naar je DB-laag maakt foutopsporing veel moeilijker dan het opvangen aan de grens.

Samenvatting

De patronen in dit artikel bestrijken het meeste wat je nodig hebt om TOON te integreren in een echte Node.js-codebase: fs voor bestands-I/O, een validateToon()-wrapper voor veilig grensparsen, een drop-in Express-middleware voor application/toon-teksten, en een DB-naar-prompt pijplijn die SQL-rijen omzet in tokenefficiënte LLM-invoer. De bibliotheek zelf — @toon-format/toon — blijft uit je weg: twee functies, geen configuratie, gooit bij ongeldige invoer. Gebruik de TOON Validator om uitvoer te controleren tijdens ontwikkeling, TOON Formatter om gecodeerde data te inspecteren, JSON naar TOON om bestaande datasets te converteren vóór ze in een prompt te plakken, en TOON naar JSON als je een gedecodeerde respons moet doorgeven aan een downstream-systeem dat JSON verwacht.