chunk-engine

Changelog

Release history for py-chunks, js-chunks and rs-chunks — one engine, three packages, one version number since 0.6.0.

View raw

Release history for all three packages. The newest entry at the top of this page is the newest release; for what a registry is serving right now, check PyPI, npm or crates.io — a publish can lag a release.

From 0.6.0 the three packages share one version number. They are one engine with three bindings and are released together, so py-chunks, js-chunks and rs-chunks now move in lockstep — js-chunks and rs-chunks jump from 0.1.0 to 0.6.0 to join py-chunks, not because 5 releases happened in between. History before 0.6.0 tracks py-chunks, the longest-running package, from which the shared engine grew. Entries below are reconstructed from git tags and commit messages; py-chunks and js-chunks also ship their own CHANGELOG files from 0.6.1.

0.6.4

Released 2026-09-12. Token-budget features in the Python and JavaScript bindings — no engine behaviour changes, so cross-SDK output is untouched.

  • fit_tokens / fitTokens now hold their budget on every input. The split ladder bottoms out at a character-level bisection sized by your own counter, so whitespace-free text — CJK prose, long URLs, base64, minified code — can no longer be silently emitted over budget as one indivisible run. Parts reassemble losslessly.
  • Pass the tokenizer you're holding. counter accepts a tiktoken Encoding, a HuggingFace tokenizer, an encoding or model name string ("cl100k_base", "gpt-4", a HF id), or a plain str -> int callable. Name resolution imports tiktoken/transformers lazily; HuggingFace counters default to add_special_tokens=False. Exported as py_chunks.coerce_counter too.
  • fit_tokens(..., overlap=) — an int token count or a fraction of the budget, shared between the parts of a chunk that had to be split. Scoped within one engine chunk on purpose (overlapping across chunks would smear metadata and structural boundaries), and overlapped parts still respect the budget — asserted by test, not assumed.
  • chunk_text(text) / chunkText(text) — chunk a bare string through the plain-text pipeline, no file or extension. Composes with fit_tokens / fitTokens.
  • The JavaScript fitTokens gains the same character-level floor and the same overlap option (an int, or a fraction of the budget), with the same ≤ budget enforcement. Counter name resolution stays Python-only — JS has no canonical tokenizer registry, so fitTokens keeps its callable contract.

0.6.3

Released 2026-09-01. Twenty correctness fixes across seventeen formats, from a per-format review of the engine against each format's specification. Every fix ships with a test that fails without it. Highlights:

  • Silent data loss closed: a spreadsheet header heuristic that deleted every row above a deep "header" row (one sheet lost 329 of 331 rows with no error); .ppt emitting slides deleted by earlier saves as live content (total_slides 305 for a 14-slide deck); CSV/TSV records beginning # silently dropped; .docx tabs and symbol-font glyphs producing no output; notebook image payloads in Jupyter's split-line form dropped; GeoJSON-style envelope keys deleted around the record array.
  • Corruption closed: RTF no longer synthesises a character from two unrelated unpaired surrogates; non-UTF-8 HTML and text now decode via a statistical charset detector instead of a blanket Windows-1252 fallback (a windows-1251 page went from 927 mojibake runs to fully recovered); JSON object members keep document order; PDF honours /CropBox so out-of-page pre-press bands are no longer extracted (clipping only under an author-declared crop); mailbox splitting no longer turns a body line starting with From into a phantom message; text/css resources in HTML email are no longer inlined as prose; a comment inside a hyperlink in .odt no longer crashes the caller.
  • .msg recipient types are read at their specified offset, so Cc: lines reappear; EPUB image order is deterministic; slide-number/date placeholder caches in PowerPoint are no longer emitted as slide text.
  • pip install py-chunks 0.6.2 could abort the host process on a malformed .xls; 0.6.3 returns a catchable error.

0.6.2

Released 2026-08-08. Argument validation, error-contract completeness and a Python 3.9 that actually imports — no new formats or modes, and no change to the chunks any already-valid call returns.

py-chunks

  • Python 3.9 actually works. import py_chunks raised TypeError: unsupported operand type(s) for | on CPython 3.9: 41 runtime-evaluated PEP 604 unions in function signatures across _sources.py and four chunker modules. from __future__ import annotations is now present in every package module, so annotations are never evaluated at definition time. The declared requires-python = ">=3.9" is a true statement again.
  • py_chunks.__version__, read from the installed distribution's metadata, so it cannot drift from what pip installed. Exported through __all__.
  • A Python 3.9 wheel-test job gates publishing, alongside the existing 3.11 job — the 3.11 job proves the stable ABI spans versions, the 3.9 job proves the Python sources contain no post-3.9 syntax.
  • Three streaming helpers skipped argument validation. stream_chunk_doc() and stream_chunk_ppt() ran no mode or range check at all, so a mis-typed mode silently streamed default-mode chunks instead of raising; stream_chunk_docx() raised NotImplementedError: Streaming for <mode> mode coming soon, which was doubly wrong — nothing was unimplemented, all seven DOCX modes stream. All three now run the same validators as their batch counterparts and raise ValueError at the call, before the first item is produced. stream_chunk_docx no longer raises NotImplementedError; if you caught it, catch ValueError. The other 16 stream_chunk_* helpers were audited and were already clean.
  • Range messages now match the engine's wording. chunkers/doc.py, chunkers/ppt.py and chunkers/docx.py said window_size must be > 0 (and the same shape for sentences_per_chunk / paragraphs_per_page), while the spreadsheet family said window_size must be >= 1. Everything now says the engine's must be greater than 0, so the sentence is identical across formats and across the three SDKs. The exception type is unchanged — keep branching on that, not on the text.

js-chunks

  • ChunkError now covers the wrapper's own argument validation. Three host-side checks — a filesystem path used off Node, an unsupported source type, and byte input with no filename — previously threw a plain Error or TypeError. They now throw ChunkError with kind: "invalid-arg". Message text is unchanged, so only the thrown type differs; code that caught TypeError for the unsupported-source case must catch ChunkError instead.
  • A missing or unreadable path is now a ChunkError too. fs.readFileSync sat outside the wrapping, so getChunks("./missing.md") escaped as a raw Node Error — the single most common failure fell outside a contract documented as complete. It now throws ChunkError with kind: "io", preserving Node's message (ENOENT: no such file or directory, open './missing.md'). With this and the three checks above, every JavaScript failure is a ChunkError. Code that tested e.code === "ENOENT" on a non-ChunkError branch should move to e instanceof ChunkError && e.kind === "io".
  • overlap >= window_size is now invalid-arg everywhere. It was kind: "parse" for the markdown-family formats and "invalid-arg" for DOCX/PPTX/XLSX, because md/txt/html lifted their builder result straight into Parse and csv::chunk_from_bytes skipped its range checks entirely. A shared validate_mode_args now runs before dispatch, and the CSV checks were added. The same applies to window_size = 0, sentences_per_chunk = 0 and paragraphs_per_page = 0. kind is now a reliable argument/parse split — see Error Handling. With the EPUB and spreadsheet fixes below, "everywhere" is now literally true.
  • Four escapes from the ChunkError contract, all through opts. getChunks(bytes, null) and getMarkdown(bytes, null) threw a raw TypeError; a throwing getter on opts propagated as whatever it threw, and streamChunks hit it too via { ...opts } even though it reads no key itself; and a Blob whose arrayBuffer() rejects escaped as its own rejection. A single snapshotOpts now runs at all five entry points — so a getter fires at most once, and never mid-parse — and the Blob read is wrapped, reporting kind: "io", the same kind the path branch reports for the same condition. null is accepted as "no options".
  • Numeric options are validated, not coerced. The wasm boundary takes usize, so windowSize: -1 used to return chunks, 2.5 truncated to 2 and "3" coerced to 3; a non-string filename or mode surfaced as kind: "unknown" with the message memory access out of bounds. All of these are now kind: "invalid-arg" with an actionable message. Out-of-range values reuse the engine's snake_case sentence (window_size must be greater than 0); type mistakes name the camelCase key you typed. Zero is deliberately still handled by the engine, so its message names the parameter the target format really uses — paragraphsPerPage: 0 on a CSV reports rows_per_chunk must be greater than 0. overlap: 0 remains legal.
  • XML entity references in OOXML relationship targets are now decoded. Hyperlink URLs in get_markdown output carried literal &amp; because relationship attributes were read without entity folding. Markdown output only; chunk content was never affected.
  • streamChunks is documented honestly in the README as an ergonomic wrapper, not incremental streaming. No API change; the TSDoc already said so.

rs-chunks — and therefore all three SDKs

  • EPUB now validates its mode arguments. EPUB does not route through the markdown pipeline, so it never reached the shared validate_mode_args, and a bad argument produced an empty chunk list instead of an error — get_chunks("book.epub", mode="sliding_window", window_size=100, overlap=100) returned []. This was true in all three SDKs: py-chunks checked the mode string in its own layer and nothing else, so the numeric arguments were unvalidated there too. A new epub::validate_args now runs at all four entry points, after the extension check and before the book is loaded, so the bytes route rejects without parsing. Valid calls are unchanged (Moby Dick still yields 1,440 chunks at the defaults). If you relied on an empty array for bad EPUB arguments, you now get ValueError / ChunkError / ChunkError::InvalidArg.
  • The spreadsheet family rejects paragraphs_per_page = 0 in page_aware. XLSX/ODS/XLSB/XLSM paginate by whole-sheet regions and never read the value, so zero was silently accepted while every other format rejected it. The check is scoped to page_aware — the only mode that reads the parameter — because an unconditional check would reject a value that no other format rejects outside its mode.
  • One sentence for a range rejection. The five spreadsheet sites that said window_size must be >= 1 now say the engine's must be greater than 0, matching the document formats and the Python layer. No exception type or kind changed.

0.6.1

Released 2026-08-08. Packaging, typing, error ergonomics and semver hardening — no new formats or modes.

js-chunks

  • Typed errors. Every engine failure now throws a ChunkError — a real Error subclass — with a kind of unsupported, invalid-arg, parse, io or unknown. Failures used to be thrown as bare strings, so e instanceof Error was false and there was no stack; both work now. ChunkError and the ChunkErrorKind type are exported. Messages are unchanged. See Error Handling.
  • A supported bundler path. New js-chunks/web subpath export (plus js-chunks/web/chunks_wasm_bg.wasm) for vite/webpack apps; the default entry still auto-loads on Node, Bun, Deno and unbundled browser ESM. See Installation.
  • chunkPdfMarkdownWithImages and normalizePdfMarkdown are now public API, joining chunkPdfMarkdown for host-parsed PDF workflows.
  • "sideEffects": false for tree-shaking. The package is ESM-only — require() needs Node ≥ 20.19.

py-chunks

  • Stable-ABI wheels. One binary wheel per platform now covers CPython 3.9+; previously only 3.13 got a wheel and everyone else fell back to a source build. Linux aarch64 wheels added.
  • The package is typedpy.typed, complete stubs for the compiled module, and the Typing :: Typed classifier, so mypy and pyright see full annotations.
  • In-memory input no longer touches disk. Bytes, file objects, uploads and URLs are passed straight to the engine instead of round-tripping through a temp file (output verified byte-identical).
  • The GIL is released while the engine parses and chunks, so a long parse no longer blocks other Python threads.

rs-chunks

  • ChunkError is now #[non_exhaustive]. A match on it in a downstream crate must carry a wildcard arm; this lets the engine add variants without a semver-major bump, and it is a breaking change for 0.6.0 code that matched exhaustively. See the Rust API reference.
  • Every dispatch entry point is panic-guarded. All six public functions run the parse behind catch_unwind, so a panic in the engine or in a third-party parser returns ChunkError::Parse("internal parser panic: …") instead of unwinding into the caller. An adversarial-input test suite came with it.
  • ChunkMode implements FromStr; the inherent from_str stays as an Option-returning convenience.
  • The temp-file fallback for unsupported bytes was removed — the bytes API is now genuinely filesystem-free.
  • Internal only: the docx and pptx monoliths were split into 24 focused submodules, and .doc opens its CFB container once over borrowed bytes instead of three times over full copies. Behaviour-identical — golden snapshot 0 changed, parity 3,222/0.

0.6.0

Released 2026-08-07. The largest correctness release so far: 64 engine changes closing every defect in the tracker. Chunk content changes for most formats, almost always because more text is now extracted or because it is laid out correctly.

Re-index after upgrading. Extraction improved across PDF, .doc, .txt, .md, email and OpenDocument, so chunk text and boundaries differ from 0.5.0. Embeddings generated with an earlier version will not match text produced by this one.

Behaviour changes worth knowing before you upgrade

  • Oversized blocks are now bounded in every mode. A table, list or code block larger than the chunk budget used to come back as a single chunk; it is now split at row, item or line boundaries — never mid-row. A single 11k-token chunk broke the size contract downstream embedding models depend on.
  • Image names are identical across the three SDKs. They were not before: the content hash included a native-width length prefix, so JavaScript produced different names from Python and Rust for every format. JS names change with this release.
  • py-chunks has no runtime dependencies. pypdfium2 is gone from the install; PDFium is vendored in the wheel.
  • Error types and messages were made consistent across bindings and modes, so code matching on exact strings may need updating.

PDF — the parser is now this project's own (lopdf object model, our layout engine, WebAssembly-clean) rather than a third-party pipeline.

Where liteparse ended up

0.5.0 routed PDF through the third-party liteparse engine, and js-chunks carried an @llamaindex/liteparse-wasm peer dependency to do the same in the browser. Both are gone. liteparse survives in rs-chunks as an optional dependency behind the pdf-native Cargo feature, used for one thing only: rasterising a page with PDFium when a scanned PDF has neither extractable text nor an embedded page image. The feature is on by default for native builds and compiled out of the WASM build, which is the one deliberate capability difference between js-chunks and the other two SDKs. Every SDK, including js-chunks, parses PDF text with the engine's own parser.

  • Standard-14 font metrics shipped: fonts that declare no /Widths no longer advance every glyph half an em, which had interleaved adjacent text runs (Science, University of OxfordScieUnnciev,ersity of Oxford).
  • Subset fonts naming glyphs by index (/g18) now decode — one 24-page paper went from 7,921 to 46,990 extracted characters.
  • Genuine two-column pages are split into columns and read down them; tables and bylines are told apart from columns and kept intact.
  • Author blocks read down the column instead of across, so each author keeps their own affiliation and email.
  • default streams incrementally: the first chunk of a 5,000-page document costs fewer than 10 pages instead of a full parse.
  • Ligatures are spelled out (classificationclassification) so words stay searchable, and hyperlink targets become Markdown links.

Legacy .doc / .ppt — two offset bugs were corrupting the start and end of virtually every real .doc, and a third meant no .doc ever had paragraph properties, which silently disabled table, list, page-break and style detection. All are fixed, and .doc now reports tables, list depth and section breadcrumbs. Footnotes, headers, annotations, endnotes and text boxes are extracted; page and slide provenance reaches every mode.

Encoding and text

  • .txt encoding is detected rather than assumed: UTF-16 via BOM and a NUL sniff, cp1252 as the 8-bit fallback, every BOM stripped.
  • Line endings are normalised once at decode — CRLF documents used to come back as one unsplit chunk, which affected every Windows-authored .txt and .md.
  • Literal & is no longer dropped from DOCX and PPTX text (R&DR D), and every named entity is decoded across HTML, XML and OpenDocument.

Spreadsheets — an unreadable sheet no longer aborts the workbook, workbooks that calamine rejects are repaired before reading, XLSX streams for real, and skipped sheets are named in the metadata.

Email, eBooks, notebooks, OpenDocument

  • .eml/.mbox: text attachments inlined, threading headers surfaced, per-message identity on every mbox chunk, and the declared charset verified rather than trusted.
  • .msg: image attachments extracted, Exchange legacyDNs no longer formatted as email addresses, and non-Latin RTF bodies no longer lost.
  • .epub: repeated metadata kept, the native TOC parsed, navigation pages flagged.
  • .ipynb: images emit a placeholder in every mode, the richest output MIME wins.
  • .odt/.odp: numbered lists stay numbered, slides carry the identity .pptx chunks already had, images are referenced and deduped.

Metadata — JSON chunks name the records they were built from (record_range), and the metadata reference documents every key per format and mode.

0.5.0

Released 2026-07-28.

  • Seven new formats, a PDF liteparse rewrite (PDF ran through the third-party liteparse engine, which vendors its own PDFium — replaced in 0.6.0 by the engine's own parser), and image extraction for spreadsheets and HTML.

0.4.7

  • Image extraction for DOC and PPT (legacy binary formats) via MS-ODRAW BLIP records (JPEG/PNG).

0.4.6

  • Documentation fixes: PPT in the package description; .pdf added to the list_images format list.

0.4.4

  • PPT (PowerPoint 97–2003) support — a pure-Rust CFB parser, all 7 document modes, streaming, and to_markdown.

0.4.3

  • PDF image extraction via list_images=True, with a scanned-PDF fallback.

0.4.2

  • Image extraction extended to XLSX and HTML; get_chunks(list_images=True) and the ChunksResult API introduced.

0.4.1

  • Image extraction via get_markdown(list_images=True) for DOCX and PPTX.

0.4.0

  • DOC (Word 97–2003) support and get_markdown() for all formats.

0.3.1

  • CSV support (row, sliding_window, page_aware).

0.3.0

  • XLSX / XLS support with 6 spreadsheet modes.

0.2.0

  • Streaming for all formats and a Rust module refactor.

On this page