Skip to content

Node.js 流式 API

pdf-oxide 原生绑定为搜索结果、页面和表格提供可读流——既符合 Node.js 管道的习惯用法,又能在处理大型文档时高效利用内存。

所有流都以对象模式实现标准的 Node.js Readable 接口,支持背压,可与 pipe() 集成,并支持 for await 异步迭代。

流是 Node.js 绑定的原生特性。对于 WASM 构建,请改用同步迭代。

SearchStream

随着底层 SearchManager 产生匹配项,逐个发出 SearchResult

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

逐页发出提取出的文本。适用于按行输出的场景,或在以限流队列向 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

每检测到一个表格就发出一个。

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;
  }
});

内存效率

流在任意时刻只保留一个结果。对于一个产生 50,000 个匹配项的 10,000 页 PDF,SearchStream 占用的内存保持恒定——整个结果集永远不会被一次性物化。

清理

关闭父级 PdfDocument 会结束所有关联的流。流还会在 end / error 时清理其对 manager 的引用。

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

相关链接