npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

js-chunks

v0.6.4

Published

Idiomatic TypeScript/JS wrapper around a WASM-backed Rust document chunking engine for RAG — 36 file formats, images, and PDF; runs on Node, Bun, Deno, and browsers.

Readme

js-chunks

Part of chunk-engine — one Rust engine, three byte-identical SDKs (py-chunks · js-chunks · rs-chunks). Full documentation, playground and benchmarks: chunkengine.dev

npm License

The JavaScript / TypeScript binding for chunk-engine. Turn any of 36 document formats into typed, structure-aware chunks for RAG — the engine is compiled to WASM, so it runs in Node, Bun, Deno, and the browser.

Install

npm install js-chunks

Quick start

import { getChunks, streamChunks, getMarkdown } from "js-chunks";

// From a path (Node / Bun only)
const chunks = await getChunks("./report.pdf", { mode: "semantic" });

for (const c of chunks) {
  console.log(c.contentType, c.metadata, c.content);
}

// From bytes — a filename is required so the engine can route by extension
const bytes = new Uint8Array(await file.arrayBuffer());
await getChunks(bytes, { filename: "report.docx" });

// Iterate chunk by chunk (ergonomic wrapper — see "Streaming" below)
for await (const chunk of streamChunks("./big.md", { mode: "section" })) {
  handle(chunk);
}

// Markdown conversion
const md = await getMarkdown("./report.docx");

In the browser, pass a File/Blob straight from an <input type="file"> — a named Blob supplies its own filename.

📖 Chunking modes · Supported formats · Output schema · Metadata reference

API

interface Chunk {
  content: string;
  contentType: string;               // camelCase (the WASM core emits content_type)
  metadata: Record<string, unknown>; // passed through untouched
}

interface ChunkImage {
  name: string;                      // matches the ![](name) markdown reference
  data: Uint8Array;
}

interface ChunkOptions {
  mode?: ChunkMode;             // default "default"
  windowSize?: number;          // default 3   — sliding_window
  overlap?: number;             // default 1   — sliding_window
  sentencesPerChunk?: number;   // default 3   — sentence
  paragraphsPerPage?: number;   // default 15  — page_aware
  filename?: string;            // required for byte sources
  listImages?: boolean;
}

// Overloaded on `listImages`:
function getChunks(source, opts?): Promise<Chunk[]>;
function getChunks(source, opts: { listImages: true }): Promise<{ chunks: Chunk[]; images: ChunkImage[] }>;
function getMarkdown(source, opts?): Promise<string>;
function getMarkdown(source, opts: { listImages: true }): Promise<{ markdown: string; images: ChunkImage[] }>;
function streamChunks(source, opts?): AsyncIterable<Chunk>;
// For markdown some *other* PDF parser produced (.pdf input needs none of these):
function chunkPdfMarkdown(markdown, totalPages, opts?): Promise<Chunk[]>;
function chunkPdfMarkdownWithImages(markdown, images, totalPages, opts?): Promise<{ chunks: Chunk[]; images: ChunkImage[] }>;
function normalizePdfMarkdown(markdown): Promise<string>;

source may be a string path (Node/Bun only), Uint8Array, ArrayBuffer, Node Buffer, or Blob.

Errors

Every failure this package raises is a ChunkError (a real Error subclass) — engine failures and host-side argument validation alike, so a single instanceof ChunkError catch is the complete contract. For engine failures the message is byte-identical to the message py-chunks raises for the same input — the cross-SDK parity contract — and the variant py-chunks expresses as an exception type is restored on kind:

import { getChunks, ChunkError, type ChunkErrorKind } from "js-chunks";

try {
  await getChunks(bytes, { filename: "data.xyz" });
} catch (e) {
  if (e instanceof ChunkError) {
    e.kind;    // "unsupported" | "invalid-arg" | "parse" | "io" | "unknown"
    e.message; // for engine errors, exactly what py-chunks raises
  }
}

Argument validation that never reaches the engine — a filesystem path off Node, an unsupported source type, byte input with no filename — has no py-chunks counterpart and is reported as kind: "invalid-arg".

Streaming

streamChunks is an ergonomic wrapper, not incremental streaming. The WASM boundary is a synchronous full-parse, so it computes the complete chunk array and then yields the elements one at a time:

for await (const chunk of streamChunks("./big.docx")) { … }
// identical results — and identical peak memory — to:
for (const chunk of await getChunks("./big.docx")) { … }

Use it for for await ergonomics and to interleave downstream work (embedding, upserting) with iteration — not to bound memory on a large file. rs-chunks and py-chunks do offer genuinely incremental streaming for some formats; WASM does not, and closing that gap would be an engine redesign rather than a wrapper change.

Images

const { chunks, images } = await getChunks("./deck.pptx", { listImages: true });
const { markdown, images: mdImages } = await getMarkdown("./deck.pptx", { listImages: true });

Formats without embedded images return an empty images array.

PDF

PDF is parsed by the engine itself, in WASM — no peer dependency, and the same code py-chunks and rs-chunks run. Over the 24-document PDF corpus, markdown, chunks and image bytes are identical to py-chunks on 23 of 24.

The exception is rendering: a scanned PDF with no text and no embedded page image is rasterised natively, which WASM cannot do, so it reports that it has no extractable text instead.

If you already have PDF markdown from another parser, chunk it directly with chunkPdfMarkdown(markdown, totalPages, opts?).

Runtimes

Works on Node, Bun, Deno, and browsers. Node and Bun use the synchronous pkg-node build; Deno and unbundled browser ESM load the async-instantiated pkg-web build automatically. WASM instantiation is lazy and cached on first call. Filesystem paths are Node/Bun only — elsewhere pass bytes with a filename.

This package is ESM-only. require("js-chunks") works on Node ≥ 20.19 and ≥ 22.12 (which can require() ESM); on older Node use import / dynamic import().

In serverless/edge contexts, run on a Node.js runtime so the WASM module loads (PDF parsing happens inside the engine — there is no peer dependency). See framework integration for Express, Fastify, Hono, Next.js, NestJS, and SvelteKit handlers.

Bundlers (vite/webpack)

The main entry's runtime auto-detection loads the web build through a dynamic import that is deliberately hidden from bundlers (@vite-ignore), so it only works where the import specifier resolves at runtime (Node, Bun, Deno, unbundled browser ESM). A bundled app would neither bundle the glue nor copy the .wasm binary — so bundled apps import the web build explicitly via the js-chunks/web subpath and serve chunks_wasm_bg.wasm as an asset:

import initWasm, * as engine from "js-chunks/web";
// vite: resolve the wasm binary to a served asset URL
import wasmUrl from "js-chunks/web/chunks_wasm_bg.wasm?url";

await initWasm({ module_or_path: wasmUrl }); // once, cached
const chunks = engine.getChunks(bytes, "report.docx", "default", 3, 1, 3, 15);

js-chunks/web is the raw wasm-bindgen surface: positional arguments, chunks as { content, content_type, metadata } (snake_case), and an init default export that must be awaited before any call. If your bundler already rewrites new URL("chunks_wasm_bg.wasm", import.meta.url) inside the glue to an asset URL, plain await initWasm() works too — the explicit ?url form above is the dependable one.

Develop

npm install
npm run build:wasm   # wasm-pack -> pkg-node / pkg-web (needs Rust + wasm-pack)
npm run build        # tsc -> dist/
npm test             # builds dist/, then vitest against the real WASM

Tests resolve fixtures from the workspace corpus (../test_files) when present, else from the committed subset in test/fixtures/ (what CI uses). Set JS_CHUNKS_FIXTURES=local to force the committed subset.

License

MIT