Przeczytałeś dokumentację, wiesz że TOON redukuje liczbę tokenów o połowę na danych tabelarycznych. Teraz chcesz faktycznie to podpiąć. Ten artykuł dotyczy instalacji: odczytu i zapisu plików .toon, walidacji TOON na granicach systemu, budowania middleware Express parsującego ciała żądań TOON i składania pipeline'u baza danych → prompt, który podaje TOON bezpośrednio do LLM. Prawdziwy kod, prawdziwe wzorce — bez przykładów zabawkowych.

Konfiguracja

Zainstaluj pakiet z npm. Jest tylko ESM, więc będziesz potrzebował "type": "module" w swoim package.json lub użyj rozszerzenia .mjs. Node.js 18+ to wszystko czego potrzebujesz — bez plików konfiguracyjnych, bez wtyczek.

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

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

Odczyt i zapis plików TOON

Moduł fs Node.js obsługuje I/O. Przekaż zawartość pliku bezpośrednio do decode(), lub przekaż swoje dane do encode() i zapisz wynik na dysk. Poniżej są oba wzorce — synchroniczny dla skryptów i narzędzi CLI, asynchroniczny dla tras serwera.

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');
}
Zawsze używaj kodowania 'utf8'. Pliki TOON są zwykłym tekstem. Pominięcie argumentu kodowania zwraca Buffer — decode() oczekuje ciągu i zgłosi błąd typu, jeśli zostanie przekazany Buffer.

Walidacja TOON na granicach systemu

decode() zgłasza błąd przy nieprawidłowym wejściu, co jest właściwym zachowaniem dla parsera, ale niewygodne na granicy API lub konsumera kolejki, gdzie potrzebujesz ustrukturyzowanego wyniku, a nie nieprzechwyconego wyjątku. Rozwiązaniem jest cienki wrapper, który zamienia zgłoszenie na wartość zwracaną. To wzorzec, po który sięgniesz w Express procesorach tras, procesorach kolejek i wszędzie tam, gdzie zewnętrzne dane wchodzą do Twojego systemu.

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

Użycie w trasie Express lub konsumerze kolejki wygląda identycznie — wywołaj validateToon(), rozgałęź na valid i albo kontynuuj z data, albo zwróć 400 / przenieś wiadomość do dead-letter z ciągiem error. Wzorzec try/catch utrzymuje kod wywołujący czysty i przewidywalny.

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

Budowanie middleware TOON dla Express

express.json() parsuje ciała application/json i umieszcza wynik na req.body. Oto to samo dla application/toon. Wstaw go przed swoimi procedurami obsługi tras, a reszta stosu nigdy nie zauważy różnicy.

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

Konwersja wyników bazy danych do TOON przed wysłaniem do LLM

To jest wzorzec, do którego TOON został zbudowany. Odpytujesz bazę danych, otrzymujesz tablicę wierszy, kodujesz do TOON i wstawiasz bezpośrednio do swojego promptu. LLM otrzymuje całą strukturę bez narzutu powtarzania kluczy JSON. Oto realistyczny pipeline używający 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);
}

Ten sam wzorzec działa dla dowolnego klienta SQL lub ORM — Prisma, Drizzle, Knex, Sequelize — o ile Twoje zapytanie zwraca zwykłe obiekty JS. encode() pobiera nazwy kluczy z pierwszego wiersza i używa ich jako nagłówków kolumn; kolejne wiersze są zapisywane jako wartości oddzielone przecinkami. Wynik zapytania 50 wierszy, który kosztowałby ~1 500 tokenów jako tablica JSON, typowo kosztuje ~600–700 tokenów jako TOON.

Obsługa błędów i przypadków brzegowych

Kilka rzeczy wartych wiedzenia przed wdrożeniem:

  • LLM zwraca zniekształcony TOON. Modele nie zawsze doskonale odtwarzają format, szczególnie przy pierwszej próbie. Opakuj decode() w try/catch (lub użyj validateToon() powyżej). Jeśli się nie powiedzie, zaloguj surową odpowiedź, zwróć błąd do wywołującego i — jeśli potrzebujesz niezawodnie ustrukturyzowanego wyjścia — dodaj powtórzenie z wyraźnym promptem korekcyjnym: "Twoja ostatnia odpowiedź nie była prawidłowym TOON. Proszę sformatuj ją ponownie."
  • Wartości zawierające przecinki lub dwukropki. TOON używa przecinków do separacji wartości i dwukropków w składni obiektowej — oba są znaczącymi znakami. encode() wykrywa je automatycznie i opakowuje wartość w podwójne cudzysłowy. Nigdy nie musisz wstępnie przetwarzać swoich danych; po prostu przekazuj surowe ciągi.
  • Null i undefined. encode() serializuje null jako null (bez cudzysłowów) i całkowicie pomija właściwości undefined — to samo zachowanie co JSON.stringify(). Podczas dekodowania, goły null jest zwracany jako JS null.
  • Puste tablice. encode([]) zwraca prawidłową pustą tablicę TOON. decode() przerabia ją z powrotem czysto. Zabezpiecz upstream, jeśli Twój prompt LLM nie powinien zawierać pustego zestawu danych.
  • Bardzo duże wyniki zapytań. Nie ma twardego limitu w bibliotece, ale LLM mają limity okna kontekstowego. Stronicuj lub LIMIT swoje zapytania przed kodowaniem — 100–200 wierszy to rozsądny pułap dla większości promptów.
Waliduj przed przechowywaniem. Jeśli Twój pipeline akceptuje TOON ze źródła zewnętrznego (webhook, kolejka, klient API) i przechowuje zdekodowany wynik w bazie danych, zawsze najpierw uruchom validateToon(). Dopuszczenie zniekształconego payloadu do warstwy DB sprawia, że debugowanie jest znacznie trudniejsze niż wychwycenie go na granicy.

Podsumowanie

Wzorce w tym artykule obejmują większość tego, czego potrzebujesz do integracji TOON w prawdziwym kodzie Node.js: fs dla I/O plików, wrapper validateToon() dla bezpiecznego parsowania granic, drop-in middleware Express dla ciał application/toon, i pipeline DB-do-promptu, który zamienia wiersze SQL w efektywne tokenowo wejście LLM. Sama biblioteka — @toon-format/toon — nie wchodzi Ci w drogę: dwie funkcje, brak konfiguracji, zgłasza błąd przy nieprawidłowym wejściu. Użyj Walidatora TOON do sprawdzania wyjść podczas tworzenia, Formattera TOON do inspekcji zakodowanych danych, JSON do TOON do konwersji istniejących zestawów danych przed wklejeniem ich do promptu i TOON do JSON jeśli potrzebujesz przekazać zdekodowaną odpowiedź do downstream systemu oczekującego JSON.