TRF is a TypeScript parser and serializer for the FIDE Tournament Report File format — the standard interchange format used by all FIDE-endorsed pairing software (JaVaFo, bbpPairings, Swiss Manager, Vega).
Parses TRF strings into a fully-typed Tournament object and serializes them
back. Zero runtime dependencies.
npm install @echecs/trfimport { parse, stringify } from '@echecs/trf';
const tournament = parse(trfString);
console.log(tournament.name); // "My Tournament"
console.log(tournament.rounds); // 9
console.log(tournament.players[0].name); // "Player0001"
console.log(tournament.players[0].rating); // 2720
console.log(tournament.players[0].results); // [{ round: 1, color: 'w', opponentId: 4, result: '1' }, ...]
const trf = stringify(tournament); // back to TRF stringimport { parse } from '@echecs/trf';
function parse(input: string, options?: ParseOptions): Tournament | null;Takes a TRF string and returns a Tournament object, or null if the input
cannot be parsed.
- Strips BOM and surrounding whitespace automatically.
- Never throws — parse failures call
options.onErrorand returnnull. - Recoverable issues (unknown tags, malformed fields) call
options.onWarningand continue parsing.
import { parse } from '@echecs/trf';
const tournament = parse(trfString, {
onError: (err) => console.error(`Parse failed: ${err.message}`),
onWarning: (warn) => console.warn(`Warning: ${warn.message}`),
});Round results on each player use the following codes:
| Code | Meaning |
|---|---|
1 |
Win |
0 |
Loss |
= |
Draw |
+ |
Forfeit win |
- |
Forfeit loss |
F |
Full-point bye |
H |
Half-point bye |
U |
Unplayed |
Z |
Zero-point bye |
import { stringify } from '@echecs/trf';
function stringify(tournament: Tournament, options?: StringifyOptions): string;Takes a Tournament object and returns a TRF16 string.
- Never throws.
- Omits optional header fields when absent.
parse(stringify(t))roundtrips cleanly for any validTournament.- Warns (via
options.onWarning) when a player string field exceeds its column width and will be truncated.
import { parse, stringify } from '@echecs/trf';
const t1 = parse(trfString)!;
// ...modify t1...
const updated = stringify(t1, {
onWarning: (w) => console.warn(w.message),
});@echecs/trf has no dependency on @echecs/swiss by design. To use a parsed
tournament as input to the Swiss pairing functions, adapt the types in your own
code:
import { parse } from '@echecs/trf';
import { dutch } from '@echecs/swiss';
import type { Tournament } from '@echecs/trf';
import type { Game, Player } from '@echecs/swiss';
function toPlayers(tournament: Tournament): Player[] {
return tournament.players.map((p) => ({
id: String(p.pairingNumber),
rating: p.rating,
}));
}
function toGames(tournament: Tournament): Game[] {
const games: Game[] = [];
for (const player of tournament.players) {
for (const result of player.results) {
if (result.color !== 'w' || result.opponentId === null) continue;
let score: 0 | 0.5 | 1;
if (result.result === '1' || result.result === '+') score = 1;
else if (result.result === '0' || result.result === '-') score = 0;
else if (result.result === '=') score = 0.5;
else continue;
games.push({
blackId: String(result.opponentId),
result: score,
round: result.round,
whiteId: String(player.pairingNumber),
});
}
}
return games;
}
const tournament = parse(trfString)!;
const pairings = dutch(toPlayers(tournament), toGames(tournament), 5);interface Tournament {
chiefArbiter?: string;
city?: string;
endDate?: string;
federation?: string;
name?: string;
players: Player[];
rounds: number;
startDate?: string;
timeControl?: string;
version: Version; // 'TRF16' | 'TRF26'
}
interface Player {
birthDate?: string;
federation?: string;
fideId?: string;
name: string;
pairingNumber: number;
points: number;
rank: number;
rating?: number;
results: RoundResult[];
sex?: Sex; // 'm' | 'w'
title?: Title; // 'GM' | 'IM' | 'FM' | ...
}
interface RoundResult {
color: 'b' | 'w' | '-'; // '-' = no color assigned (bye/unplayed)
opponentId: number | null; // null for byes
result: ResultCode;
round: number;
}
interface ParseWarning {
column: number; // 1-based column in the source
line: number; // 1-based line in the source (player index for stringify)
message: string;
offset: number; // byte offset in the source (0 for stringify)
}
interface StringifyOptions {
onWarning?: (warning: ParseWarning) => void;
}The Tournament Report File (TRF) format is defined in the FIDE Handbook. The current version is TRF16; TRF26 was introduced alongside the 2026 Dutch pairing rules.
MIT