docling.rs 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -159,12 +159,22 @@ import { Pipeline } from 'docling.rs'
159
159
 
160
160
  const pipeline = new Pipeline({ strict: true })
161
161
  for (const path of pdfPaths) {
162
- const { content } = pipeline.convertFile(path, { to: 'markdown' }) // warm models
162
+ const { content } = await pipeline.convertFileAsync(path, { to: 'json' }) // warm models, off the event loop
163
+ }
164
+
165
+ // Or stream a PDF's Markdown as pages finish converting:
166
+ for await (const chunk of pipeline.streamFileMarkdown('paper.pdf')) {
167
+ process.stdout.write(chunk)
163
168
  }
164
169
  ```
165
170
 
166
- `Pipeline` handles `pdf` and `image` inputs (the ML pipeline) and is synchronous
167
- reuse one instance behind a job queue.
171
+ `Pipeline` handles `pdf` and `image` inputs (the ML pipeline). The sync
172
+ `convertFile` / `convert` block the event loop; the `*Async` variants run on the
173
+ libuv thread pool, and `streamFileMarkdown` yields Markdown chunks in document
174
+ order as pages finish. Conversions on one instance run one at a time (the
175
+ models are mutable sessions) — overlapping `*Async` calls queue in submission
176
+ order, so batch throughput comes from keeping the models warm, not from
177
+ parallel calls.
168
178
 
169
179
  ### Images
170
180
 
@@ -199,7 +209,8 @@ JSON output always embeds extracted images as data URIs.
199
209
  | `checkDependencies(options?)` | `DependencyStatus` | Report which PDF/image deps are present. |
200
210
 
201
211
  `Pipeline` is the reusable warm PDF/image converter: `new Pipeline(converterOptions)`
202
- then `convertFile` / `convert`.
212
+ then `convertFile` / `convert` / `convertFileAsync` / `convertAsync` /
213
+ `convertFileStreaming` / `streamFileMarkdown`.
203
214
 
204
215
  `DocumentConverter` is the reusable form: `new DocumentConverter(converterOptions)`
205
216
  then `convert` / `convertFile` / `convertFileAsync` / `convertAsync` /
package/index.d.ts CHANGED
@@ -37,15 +37,40 @@ export declare class DocumentConverter {
37
37
  convertFileStreaming(path: string, callback: StreamCallback, options?: OutputOptions | null): void
38
38
  }
39
39
 
40
+ /** Output options for {@link Pipeline.streamFileMarkdown} (streamable modes only). */
41
+ export interface PipelineStreamOptions {
42
+ imageMode?: 'placeholder' | 'embedded'
43
+ artifactsDir?: string
44
+ }
45
+
40
46
  /**
41
47
  * A reusable PDF/image pipeline that keeps the ONNX models loaded across calls.
42
48
  * Use instead of the per-call functions when converting many PDFs/images — the
43
49
  * one-shot path reloads every model each call. Handles `pdf` and `image` inputs.
50
+ *
51
+ * The `*Async` variants run the conversion off the event loop; overlapping
52
+ * calls on one instance queue (the models are mutable sessions), so batch
53
+ * throughput comes from keeping the models warm, not from parallel calls.
44
54
  */
45
55
  export declare class Pipeline {
46
56
  constructor(options?: ConverterOptions | null)
47
57
  convertFile(path: string, options?: OutputOptions | null): ConvertResult
48
58
  convert(input: ConvertInput, options?: OutputOptions | null): ConvertResult
59
+ /** Async (Promise) file conversion on the warm pipeline, off the event loop. */
60
+ convertFileAsync(path: string, options?: OutputOptions | null): Promise<ConvertResult>
61
+ /** Async (Promise) bytes conversion on the warm pipeline, off the event loop. */
62
+ convertAsync(input: ConvertInput, options?: OutputOptions | null): Promise<ConvertResult>
63
+ /** Callback-form streaming (prefer {@link Pipeline.streamFileMarkdown}). */
64
+ convertFileStreaming(path: string, callback: StreamCallback, options?: OutputOptions | null): void
65
+ /**
66
+ * Stream a PDF's Markdown in chunks through the warm pipeline, in document
67
+ * order, as pages finish converting (an image arrives as a single chunk).
68
+ * Concatenating the chunks reproduces the buffered Markdown byte-for-byte.
69
+ */
70
+ streamFileMarkdown(
71
+ filePath: string,
72
+ options?: PipelineStreamOptions,
73
+ ): AsyncGenerator<string, void, unknown>
49
74
  }
50
75
 
51
76
  // --- dependency provisioning (PDF/image ML pipeline) -----------------------
package/index.js CHANGED
@@ -5,7 +5,8 @@
5
5
  // 1. dependency guards — converting a PDF/image/METS input throws a clear
6
6
  // error unless the ML models + pdfium are on disk (see
7
7
  // scripts/download_dependencies.sh);
8
- // 2. a `streamFileMarkdown` async generator over Markdown chunks.
8
+ // 2. `streamFileMarkdown` async generators over Markdown chunks (module-level
9
+ // and on the warm `Pipeline`).
9
10
  //
10
11
  // Works in Node.js and Bun (Bun implements N-API).
11
12
 
@@ -23,6 +24,48 @@ function mlFormatOf(name, format) {
23
24
  return native.formatFromName(name || '') || ''
24
25
  }
25
26
 
27
+ // Bridge a native (err, chunk) streaming callback into an async generator.
28
+ // `start` receives the callback and kicks off the native conversion. Chunks
29
+ // are delivered on the event loop (via a threadsafe function); a null chunk
30
+ // ends the stream, a non-null err ends it with a throw.
31
+ async function* chunkStream(start) {
32
+ const queue = []
33
+ let done = false
34
+ let failure = null
35
+ let notify = null
36
+ const wake = () => {
37
+ if (notify) {
38
+ const n = notify
39
+ notify = null
40
+ n()
41
+ }
42
+ }
43
+
44
+ start((err, chunk) => {
45
+ if (err) {
46
+ failure = err
47
+ done = true
48
+ } else if (chunk === null || chunk === undefined) {
49
+ done = true
50
+ } else {
51
+ queue.push(chunk)
52
+ }
53
+ wake()
54
+ })
55
+
56
+ while (true) {
57
+ if (queue.length > 0) {
58
+ yield queue.shift()
59
+ continue
60
+ }
61
+ if (failure) throw failure
62
+ if (done) return
63
+ await new Promise((resolve) => {
64
+ notify = resolve
65
+ })
66
+ }
67
+ }
68
+
26
69
  // --- guarded one-shot functions --------------------------------------------
27
70
 
28
71
  function convertFile(path, options) {
@@ -94,6 +137,41 @@ class Pipeline {
94
137
  assertMlReady(mlFormatOf(input && input.name, input && input.format))
95
138
  return this._inner.convert(input, options)
96
139
  }
140
+
141
+ // async so a guard failure surfaces as a rejected promise, not a sync throw.
142
+ async convertFileAsync(path, options) {
143
+ assertMlReady(mlFormatOf(path))
144
+ return this._inner.convertFileAsync(path, options)
145
+ }
146
+
147
+ async convertAsync(input, options) {
148
+ assertMlReady(mlFormatOf(input && input.name, input && input.format))
149
+ return this._inner.convertAsync(input, options)
150
+ }
151
+
152
+ convertFileStreaming(path, callback, options) {
153
+ assertMlReady(mlFormatOf(path))
154
+ return this._inner.convertFileStreaming(path, callback, options)
155
+ }
156
+
157
+ /**
158
+ * Stream a PDF's Markdown in chunks through the warm pipeline, in document
159
+ * order, as pages finish converting (an image arrives as a single chunk).
160
+ * Same contract as the module-level `streamFileMarkdown`, but reusing this
161
+ * instance's loaded models — no per-call model reload.
162
+ *
163
+ * @param {string} filePath
164
+ * @param {object} [options] output options (`imageMode`: `placeholder` or
165
+ * `embedded`; `referenced` is rejected)
166
+ * @returns {AsyncGenerator<string, void, unknown>}
167
+ */
168
+ async *streamFileMarkdown(filePath, options = {}) {
169
+ assertMlReady(mlFormatOf(filePath))
170
+ const { imageMode, artifactsDir } = options
171
+ yield* chunkStream((callback) =>
172
+ this._inner.convertFileStreaming(filePath, callback, { imageMode, artifactsDir }),
173
+ )
174
+ }
97
175
  }
98
176
 
99
177
  // --- streaming --------------------------------------------------------------
@@ -113,49 +191,9 @@ async function* streamFileMarkdown(filePath, options = {}) {
113
191
  assertMlReady(mlFormatOf(filePath))
114
192
  const { strict, fetchImages, allowedFormats, imageMode, artifactsDir } = options
115
193
  const converter = new native.DocumentConverter({ strict, fetchImages, allowedFormats })
116
-
117
- // Bridge the native (err, chunk) callback into an async generator. Chunks are
118
- // delivered on the event loop (via a threadsafe function); a null chunk ends
119
- // the stream, a non-null err ends it with a throw.
120
- const queue = []
121
- let done = false
122
- let failure = null
123
- let notify = null
124
- const wake = () => {
125
- if (notify) {
126
- const n = notify
127
- notify = null
128
- n()
129
- }
130
- }
131
-
132
- converter.convertFileStreaming(
133
- filePath,
134
- (err, chunk) => {
135
- if (err) {
136
- failure = err
137
- done = true
138
- } else if (chunk === null || chunk === undefined) {
139
- done = true
140
- } else {
141
- queue.push(chunk)
142
- }
143
- wake()
144
- },
145
- { imageMode, artifactsDir },
194
+ yield* chunkStream((callback) =>
195
+ converter.convertFileStreaming(filePath, callback, { imageMode, artifactsDir }),
146
196
  )
147
-
148
- while (true) {
149
- if (queue.length > 0) {
150
- yield queue.shift()
151
- continue
152
- }
153
- if (failure) throw failure
154
- if (done) return
155
- await new Promise((resolve) => {
156
- notify = resolve
157
- })
158
- }
159
197
  }
160
198
 
161
199
  // --- exports (explicit, so ESM named imports work in Node and Bun) ----------
package/native.d.ts CHANGED
@@ -154,4 +154,25 @@ export declare class Pipeline {
154
154
  convertFile(path: string, options?: OutputOptions | undefined | null): ConvertResult
155
155
  /** Convert PDF or image bytes, reusing the warm models. */
156
156
  convert(input: ConvertInput, options?: OutputOptions | undefined | null): ConvertResult
157
+ /**
158
+ * Async (Promise-returning) file conversion on the warm pipeline. The
159
+ * CPU-bound work runs on the libuv thread pool, keeping the event loop
160
+ * free; calls on the same instance run one at a time (the models are
161
+ * mutable sessions), so overlapping Promises queue in submission order.
162
+ */
163
+ convertFileAsync(path: string, options?: OutputOptions | undefined | null): Promise<ConvertResult>
164
+ /** Async (Promise-returning) bytes conversion on the warm pipeline. */
165
+ convertAsync(input: ConvertInput, options?: OutputOptions | undefined | null): Promise<ConvertResult>
166
+ /**
167
+ * Stream a PDF's Markdown in chunks through the warm pipeline, in document
168
+ * order, as pages finish converting (an image converts in one step and
169
+ * arrives as a single chunk).
170
+ *
171
+ * `callback` is invoked as `(err, chunk)`: once per Markdown chunk with
172
+ * `chunk` a string, once with `chunk === null` at the end, or once with a
173
+ * non-null `err` on failure. Only `placeholder` / `embedded` image modes
174
+ * stream; `referenced` is rejected. Prefer the `streamFileMarkdown`
175
+ * async-generator wrapper in JS over calling this directly.
176
+ */
177
+ convertFileStreaming(path: string, callback: (err: Error | null, arg: string | undefined | null) => any, options?: OutputOptions | undefined | null): void
157
178
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docling.rs",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Node.js / Bun bindings for docling.rs — a Rust port of docling. Convert Markdown, HTML, DOCX, PPTX, XLSX, PDF, images and more into a unified DoclingDocument (Markdown or docling-core JSON).",
5
5
  "keywords": [
6
6
  "docling",
@@ -64,8 +64,8 @@
64
64
  "@napi-rs/cli": "^2.18.4"
65
65
  },
66
66
  "optionalDependencies": {
67
- "docling.rs-linux-x64-gnu": "0.32.0",
68
- "docling.rs-linux-arm64-gnu": "0.32.0",
69
- "docling.rs-win32-x64-msvc": "0.32.0"
67
+ "docling.rs-linux-x64-gnu": "0.33.0",
68
+ "docling.rs-linux-arm64-gnu": "0.33.0",
69
+ "docling.rs-win32-x64-msvc": "0.33.0"
70
70
  }
71
71
  }