Node.js ストリーム API
pdf-oxide のネイティブバインディングは、検索結果、ページ、テーブル用の読み取り可能ストリームを提供します。Node.js のパイプラインに馴染みやすく、大きなドキュメントでもメモリ効率に優れています。
すべてのストリームは標準的な Node.js の Readable インターフェースをオブジェクトモードで実装し、バックプレッシャーに対応し、pipe() と統合でき、for await による非同期イテレーションでも動作します。
ストリームは Node.js バインディング固有の機能です。WASM ビルドでは同期的にイテレートしてください。
SearchStream
基盤となる SearchManager がマッチを生成するたびに、SearchResult を 1 件ずつ出力します。
const { PdfDocument, SearchManager, SearchStream } = require("pdf-oxide");
const doc = new PdfDocument("large.pdf");
const manager = new SearchManager(doc);
const stream = new SearchStream(manager, "invoice");
stream.on("data", (r) => {
console.log(`page ${r.pageIndex + 1}: ${r.text}`);
});
stream.on("end", () => {
console.log("search complete");
doc.close();
});
stream.on("error", (err) => {
console.error(err);
doc.close();
});
大文字小文字を区別する検索
const stream = new SearchStream(manager, "Invoice", { caseSensitive: true });
非同期イテレーション
for await (const result of stream) {
if (result.pageIndex > 50) break;
console.log(result.text);
}
pipe() との互換性
const { Writable } = require("stream");
const sink = new Writable({
objectMode: true,
write(result, _enc, cb) {
console.log(`${result.pageIndex}:${result.text}`);
cb();
},
});
stream.pipe(sink);
PageIteratorStream
1 ページ分の抽出テキストを 1 件ずつ出力します。行単位の出力や、レート制限付きキューで LLM にデータを供給する場合に便利です。
const { PageIteratorStream } = require("pdf-oxide");
const stream = new PageIteratorStream(doc, { format: "markdown" });
for await (const { pageIndex, content } of stream) {
await indexPage(pageIndex, content);
}
format には "text"(デフォルト)、"markdown"、"html"、"plain" を指定できます。
TableStream
テーブルが検出されるたびに、1 件ずつ出力します。
const { TableStream } = require("pdf-oxide");
const stream = new TableStream(doc);
stream.on("data", (table) => {
console.log(`${table.rows.length}x${table.rows[0].length} on page ${table.pageIndex}`);
});
バックプレッシャー
すべてのストリームは標準的な Node.js のバックプレッシャーを実装しています。コンシューマーの処理が遅い場合、ストリームは .read() が再開するまで抽出を一時停止します:
stream.on("data", async (result) => {
stream.pause();
await slowIndex(result);
stream.resume();
});
あるいは for await を使えば、一時停止は自動的に処理されます。
エラー処理
抽出中のエラーは標準の error イベントとして発行されます:
stream.on("error", (err) => {
if (err.code === "PDF_INVALID_PAGE") {
console.warn("skipping invalid page", err.pageIndex);
} else {
throw err;
}
});
メモリ効率
ストリームは常に 1 件の結果のみを処理中に保持します。50,000 件のマッチを生成する 10,000 ページの PDF でも、SearchStream は一定のメモリしか使用しません。結果セット全体がメモリ上に展開されることはありません。
クリーンアップ
親となる PdfDocument をクローズすると、それに紐づくすべてのストリームが終了します。ストリームは end / error のタイミングでマネージャーへの参照もクリーンアップします。
const doc = new PdfDocument("big.pdf");
const stream = new SearchStream(new SearchManager(doc), "TODO");
stream.on("end", () => doc.close());
stream.on("error", () => doc.close());
Node.js 22 以降では、using キーワードを使うとスコープを抜けたときにドキュメントが解放されます:
{
using doc = new PdfDocument("big.pdf");
const stream = new SearchStream(new SearchManager(doc), "TODO");
for await (const r of stream) console.log(r);
} // doc.close() called automatically
関連項目
- Node.js 入門 — インストール、クイックスタート
- Node.js API リファレンス
- 検索 — 非ストリーミングの検索オプション