All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
3.12.2 - 2026-04-09
- updated description to mention stringify
- removed inaccurate
fidekeyword - added
serializerkeyword
3.12.1 - 2026-04-09
- added missing type exports (
Disambiguation,File,PieceChar,Rank,Square) - replaced non-existent
Piecetype withPieceChar - corrected
Movefield types (booleannottrue) - corrected
Move.promotiontype toPieceChar - corrected
ArrowandSquareAnnotationfield types toSquare
3.12.0 - 2026-03-18
stringify()now escapes\and"in tag values. Previously, tag values containing these characters produced invalid PGN that could not be re-parsed (round-trip bug).
stream()is deprecated. Useparse()instead — it already handles multi-game input.stream()now emits a one-timeconsole.warnon first call and will be removed in the next major version.
- Internal refactoring:
parse.tsandstringify.tssplit into focused modules (constants.ts,warnings.ts,comments.ts,san.ts,tags.ts). No public API changes. - Removed unreachable runtime guard on
move.toin SAN serialization —move.to: Squareis required by the type system.
3.11.0 - 2026-03-18
StringifyOptionstype exported — a focused subset ofParseOptionscontaining onlyonWarning.stringify()now acceptsStringifyOptionsinstead of the broaderParseOptions(backwards compatible — anyParseOptionsvalue is still valid).- New exported types:
Meta,Move,MoveList,MovePair,Variation,Piece,Result. Consumers can now type variables holding parsed game data without workarounds.
parse()now usesinput.trim()instead ofinput.replaceAll(/^\s+|\s+$/g, '')— semantically identical, cleaner.%-prefixed escape lines (ESCAPErule per PGN spec §6) are now explicitly tested — both between moves and between games.stream()JSDoc clarified:onWarningis forwarded toparse()for each game, not onlyonError.
3.10.1 - 2026-03-17
- Build tool replaced with
tsdown. The published package now ships a single bundled, minifieddist/index.jsinstead of multiple unbundled files. Published package size reduced from ~200KB to ~42KB (79% reduction).dist/grammar.cjs.map(84KB) is no longer published. Public API unchanged.
3.10.0 - 2026-03-17
stringify(input: PGN | PGN[], options?: StringifyOptions): string— converts parsed PGN objects back to valid PGN strings (semantic round-trip fidelity). Accepts a single game or an array of games. Reconstructs SAN fromMovefields, re-serializes annotation commands ([%cal],[%csl],[%clk],[%eval]) back into comment blocks, and preserves RAVs and NAGs. FiresonWarningfor recoverable issues (invalid castling destination, negative clock).
src/index.tsrefactored into focused internal modules (src/types.ts,src/parse.ts,src/stream.ts,src/stringify.ts). Public API unchanged.
- Tagless games with no moves (e.g.
[Result "*"]\n\n*) now parse correctly. Previously the grammar required at least one move in the movetext.
3.9.1 - 2026-03-16
AnnotationColornow includes'C'(cyan) and'O'(orange) in addition to'B','G','R','Y'— matching real-world Lichess and ChessBase exports. Tokens with these colors were previously silently dropped.- Empty
[%csl ]and[%cal ]commands (with no token list) are now silently stripped from the comment text instead of leaking through as raw strings.
3.9.0 - 2026-03-16
Movenow exposes structured fields parsed from embedded PGN comment commands:arrows?: Arrow[]— from[%cal ...](coloured arrows on board)squares?: SquareAnnotation[]— from[%csl ...](coloured square highlights)clock?: number— from[%clk ...](remaining time in seconds, sub-second precision preserved)eval?: Eval— from[%eval ...](engine evaluation: centipawns or mate-in-N, with optional search depth)
- New exported types:
AnnotationColor,Arrow,SquareAnnotation,Eval - Command strings are stripped from
move.comment; unknown[%...]commands are left in the comment string unchanged - De-facto standard followed: python-chess
3.8.3 - 2026-03-15
stream()now accepts a Web StreamsReadableStream<string>in addition toAsyncIterable<string>. This coversfetch().body.pipeThrough(new TextDecoderStream())in browser and edge runtimes. The type signature is widened accordingly — no behaviour change for existingAsyncIterablecallers.
3.8.2 - 2026-03-15
comment_multilinenow handles one level of nested braces ({ see {Fischer} 1972 }). Previously the comment terminated at the first}, causing the rest of the game to fail to parse. Real-world PGN from GUI exports commonly contains embedded{…}spans inside comments.
3.8.1 - 2026-03-15
PIECE_MOVEno longer accepts promotion syntax (=Q,=N, etc.) on non-pawn pieces.Nf3=Qpreviously parsed silently and produced aMoveobject with a nonsensicalpromotionfield; it now causes a parse failure. OnlyPAWN_PUSHandPAWN_CAPTUREaccept thePROMOsuffix, as required by the PGN spec.Meta.Resultis now typed as optional (Result?: Result) to reflect that tagless games (no tag pairs) returnmeta: {}with noResultkey. The field was previously typed as required, which was incorrect at runtime.
3.8.0 - 2026-03-15
onWarningnow fires for move number mismatches (e.g.5. e4appearing as the first move). Previously emitted unconditionally toconsole.warn; now routed throughonWarningwhen provided.console.warnis no longer called for move number mismatches — if you relied on it, add anonWarningcallback.onWarningfires when the[Result "..."]tag value does not match the game termination marker at the end of the movetext (e.g.[Result "1/2-1/2"]with a1-0termination marker).onWarningfires for duplicate tag names (same tag appearing more than once in the tag pair section). Thelineandcolumnfields point to the opening[of the duplicate — exact source position, not a nominal placeholder.
- Move number mismatch no longer emits to
console.warnwhenonWarningis not provided. It is now silently ignored, consistent with how missing STR tag warnings are handled.
3.7.0 - 2026-03-15
onWarningoption forparse()andstream(): fires once per missing STR tag. Emitted in alphabetical key order:Black,Date,Event,Result,Round,Site,White.ParseWarningis now an exported type with the same shape asParseError.
parse()andstream()now strip a UTF-8 BOM (\uFEFF) at the start of input. Chessbase and Windows editors commonly produce BOM-prefixed PGN files that previously failed silently.- Tag values containing escaped quotes (
\") or escaped backslashes (\\) now parse correctly per PGN spec section 7. - Games with no tag pairs (bare move list + result) now parse correctly per PGN
spec section 8.1 ("zero or more tag pairs"). These games return
meta: {}.
3.6.2 - 2026-03-15
pairMoves: replaceddelete move.number; delete move.longwith an explicit clean output object constructed from only the known publicMovefields.deletewas fragmenting V8 hidden classes across move objects with different optional fields (e.g. promotion moves), causing megamorphic deoptimisation and GC pressure.promotion.pgngap vspgn-parserrestored from 1.26x to 1.06x;long.pgnandtwic.pgnalso improved.
3.6.1 - 2026-03-15
stream(): result tokens straddling a chunk boundary (e.g.1at end of chunk N,-0at start of chunk N+1) were silently missed, causing the two affected games to be merged and the second game to be lost. The boundary scanner now looks back up to 6 characters into already-scanned content on each chunk to catch split tokens.stream():onErrorwas incorrectly forwarded to the remainder-flush path, causing it to fire for truncated streams (input ending without a result token) — which is expected behaviour, not a parse error. The remainder-flush path now callsparse()without options.toParseError:ParseError.offsetwas always0— the field was being read from the top level of the Peggy error object, which does not exist. It is now correctly read fromlocation.start.offset.
3.6.0 - 2026-03-15
onErroroption forparse()andstream(): passonError: (err: ParseError) => voidto observe parse failures instead of silently receiving[].ParseErrorcarriesmessage,offset,line, andcolumnfrom the Peggy parser.ParseErrorandParseOptionsare now exported types.
stream()accepts an optional second argumentoptions?: ParseOptions(backward-compatible).
- Grammar
MOVEaction block: eliminated per-move object spread ({ ...san }) and replacedfilter/joinchains with explicit loops — reduces heap allocation on every move. pairMoves: pre-sized accumulator withnew Array(...)and removed rest-spread destructuring — avoids per-move object creation and reduces V8 array resizing.stream()boundary scanner: now O(n) per chunk — regex is only attempted at characters that can start a result token (1,0,*) rather than at every depth-0 character.
3.5.3 - 2026-03-14
- Castling moves with check or checkmate indicators (
O-O+,O-O#,O-O-O+,O-O-O#) now correctly setcheck: true/checkmate: trueon the returned Move object — previously the indicator was consumed and silently discarded - Security: pin
vite>=6.4.1,rollup>=4.59.0,glob>=10.5.0via pnpm overrides to resolve 8 Dependabot CVEs in devDependencies
- Grammar: extract
applyIndicatorspreamble helper to eliminate repeatedpromo/indlogic across all SAN action blocks
- Add
vitest.config.tsto excludegrammar.cjsand bench files from coverage reporting — coverage now reflects only authored source (index.ts) - Add explicit SAN unit tests for all grammar alternatives and indicator combinations, covering patterns that snapshot tests cannot detect regressions in (full-square disambiguation, castling indicators, promotion+checkmate, etc.)
3.5.2 - 2026-03-14
- Benchmark:
benko.pgnmoved to multi-game group and compared with the correctparseGamesAPI — previously calledparseGame(single-game), causing all comparison parsers to error - Benchmark:
comments.pgnBOM (U+FEFF) now stripped before parsing, enabling comparison against@mliebelt/pgn-parserandpgn-parser - Benchmark: fixture exclusion reasons documented in
BENCHMARK_RESULTS.mdand in bench source comments
3.5.1 - 2026-03-14
- README: document
stream()API with signature and Node.js usage example - README: update type names (
Moves→MoveList) and clarifyMove.fromdisambiguation and move tuple slot semantics - Updated benchmark results for v3.5.0 SAN rule restructure
3.5.0 - 2026-03-14
stream(input: AsyncIterable<string>): AsyncGenerator<PGN>— new named export for incremental, memory-efficient parsing of large PGN databases
Move.fromwidened fromFile | RanktoDisambiguation(Square | File | Rank) to correctly type fully-disambiguated moves (e.g.Qd1xe4→from: "d1")type Movesrenamed toMoveList; newMovePair = [number, Move | undefined, Move?]tupletype Variationsimplified toMoveList[]
- Restructured
SANgrammar rule to eliminate post-match regex on every move; closes remaining ~1.1–1.2x gap vspgn-parseron move-heavy fixtures
3.4.0 - 2026-02-21
- Rewrote README following
@echecs/elolibrary style with badges, Why, Quick Start, Usage, and Contributing sections - Updated AGENTS.md to reflect Peggy migration and remove stale nearley/moo references
- Features section in README highlighting RAV and NAG support with a parser comparison table
- Performance section in README with benchmark results table
- Codecov badge to README
docs.ymlworkflow (no hosted docs in this project)
3.3.0 - 2026-02-21
- Peggy PEG parser replacing nearley/moo for O(n) linear-time parsing — delivering up to 10× throughput improvement on large PGN files
- Comparative benchmark (
comparison.bench.ts) measuring@echecs/pgnagainst@mliebelt/pgn-parserandchess.js
- Replaced
pickBywith direct property assignment in SAN action block, reducing allocations per move - Added length-check guards before NAG and comment processing in MOVE action, skipping unnecessary work for moves without annotations
- Removed
deletemutations and reduced allocations inpairMoves, avoiding V8 hidden-class transitions
- Stale
tokenizer.tsdebug script
3.2.1 - 2026-02-20
- Sort
multiGameFixtureskeys in comparison benchmark to satisfy thesort-keyslint rule
3.2.0 - 2026-02-20
- Comparative PGN parser benchmark (
comparison.bench.ts) for cross-library performance tracking
- Reduced Earley parser overhead via grammar and caching optimizations
3.1.3 - 2025-03-27
- Removed accidental production dependency introduced in 3.1.2
3.1.2 - 2025-03-01
- Increased per-test timeout to accommodate
long.pgn(~3 500 games) on slow CI runners
3.1.1 - 2025-03-01
- Corrected
.jsextension on relative imports in test files (NodeNext resolution)
3.1.0 - 2025-03-01
- moo tokenizer for faster lexing
- New grammar supporting the full PGN specification including RAV (recursive annotated variations) and NAG (numeric annotation glyphs)