docling-serve-client 0.0.0-stage → 0.4.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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Martin Winkler
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,287 @@
1
- # Temporary Holding Version
1
+ [![Test](https://github.com/martin-winkler-dev/docling-serve-client-js/actions/workflows/test.yml/badge.svg)](https://github.com/martin-winkler-dev/docling-serve-client-js/actions/workflows/test.yml)
2
+ [![Package](https://github.com/martin-winkler-dev/docling-serve-client-js/actions/workflows/package.yml/badge.svg)](https://github.com/martin-winkler-dev/docling-serve-client-js/actions/workflows/package.yml)
3
+ [![Bun](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmartin-winkler-dev%2Fdocling-serve-client-js%2Fmain%2Fpackage.json&query=%24.engines.bun&label=Bun&logo=bun&color=blue)](https://bun.sh)
4
+ [![Node.js](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmartin-winkler-dev%2Fdocling-serve-client-js%2Fmain%2Fpackage.json&query=%24.engines.node&label=Node.js&logo=nodedotjs&color=blue)](https://nodejs.org)
5
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
2
6
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
7
+ # docling-serve-client
8
+
9
+ Typed HTTP client for a running [docling-serve](https://github.com/docling-project/docling-serve): **PDF or image
10
+ bytes in, docling's Markdown or DoclingDocument JSON out**.
11
+
12
+ - Two outputs: `convertMarkdown` returns docling's Markdown unchanged except for image handling; `convertDocument`
13
+ returns the DoclingDocument (the JSON AST with labels, reading order, pages and bounding boxes) as docling sends it.
14
+ - No cleanup, no heuristics on text. Repairs and analysis belong in the code that uses this client. The Markdown is
15
+ docling's raw output, e.g. with `&` entities, soft hyphens and dot leaders left in.
16
+ - In-memory only: no file I/O, no streaming.
17
+ - **Calls never throw on runtime failures.** Every call resolves to `{ ok: true, data } | { ok: false, error }`.
18
+ Two exceptions are programming errors: the factory throws a `TypeError` on an invalid config, and a call with an
19
+ invalid option throws a `TypeError` (the promise rejects) before it sends anything.
20
+ - Sync (`convert*`) and async (`submit*` + `check`). No built-in polling, queueing or rate limiting.
21
+ - Zero dependencies. ESM, works in Node ≥ 22.12 (also via `require()`) and Bun.
22
+
23
+ ## Usage
24
+
25
+ ```ts
26
+ import { createDoclingClient } from 'docling-serve-client';
27
+
28
+ const docling = createDoclingClient({ baseUrl: 'http://127.0.0.1:5001' });
29
+
30
+ // Input is always a Uint8Array (Node Buffer is one): a PDF, or a PNG, JPEG, TIFF or WebP image
31
+ const pdf = await readFile('paper.pdf'); // node:fs/promises
32
+ // const pdf = await Bun.file('paper.pdf').bytes(); // Bun
33
+ // const pdf = new Uint8Array(await (await fetch(pdfUrl)).arrayBuffer()); // any download
34
+
35
+ const md = await docling.convertMarkdown(pdf);
36
+ if (md.ok) console.log(md.data.markdown, md.data.warnings);
37
+
38
+ const doc = await docling.convertDocument(pdf);
39
+ if (doc.ok) {
40
+ for (const item of doc.data.document.texts) {
41
+ if (item.label === 'section_header') console.log(item.level, item.text, item.prov[0]?.page_no);
42
+ }
43
+ } else {
44
+ console.error(doc.error.code, doc.error.message);
45
+ }
46
+ ```
47
+
48
+ ### Async
49
+
50
+ `submitMarkdown()` and `submitDocument()` return a plain JSON job object. Store all of it and pass it back to
51
+ `check()`. A Markdown job carries its image handling, so `check()` works the same from another process or after a
52
+ restart. `check()` returns the result type that matches the job's `kind`.
53
+
54
+ ```ts
55
+ const job = await docling.submitDocument(pdf);
56
+ if (!job.ok) return handle(job.error);
57
+ await save(JSON.stringify(job.data)); // { kind: 'document', id }
58
+
59
+ // later, as often as you like:
60
+ const state = await docling.check(JSON.parse(await load()));
61
+ if (state.ok) {
62
+ switch (state.data.status) {
63
+ case 'queued': /* state.data.position */ break;
64
+ case 'running': break;
65
+ case 'done': use(state.data.document); break;
66
+ }
67
+ }
68
+ ```
69
+
70
+ docling-serve deletes a result `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds (default 300) after its first fetch
71
+ (`DOCLING_SERVE_SINGLE_USE_RESULTS=true`, the default). A `check()` whose result download failed (network error,
72
+ `timeoutMs`, abort) can be repeated within that time; later `check()` returns `not_found` (`Result already fetched or
73
+ expired`), so persist the `done` data. Start docling-serve with `DOCLING_SERVE_SINGLE_USE_RESULTS=false` to keep results
74
+ until they expire. These are server settings; `convert*()` is not affected.
75
+
76
+ ### Options
77
+
78
+ Set docling options on the factory and override them per call, key by key; `ocrLang`, `pageRange` and
79
+ `documentTimeout` are factory-only and reject per call. Nothing is silently ignored or converted:
80
+ an unknown option or an invalid value throws a `TypeError` at the start of the call, before any request
81
+ (`Option "x" is not supported`, `Invalid value for option "x"`; nested keys are named like `imageOptions.prefix`).
82
+
83
+ ```ts
84
+ const docling = createDoclingClient({
85
+ baseUrl: 'http://127.0.0.1:5001',
86
+ apiKey: '…', // only if docling-serve requires X-Api-Key
87
+ timeoutMs: 120_000, // optional limit per request; on expiry the call resolves with `aborted`
88
+ options: { ocrLang: ['de', 'en'] }, // docling defaults for every call
89
+ fetch: myFetch, // optional custom fetch
90
+ });
91
+
92
+ await docling.convertDocument(pdf, { ocr: false, images: 'placeholder', signal: AbortSignal.timeout(60_000) });
93
+ ```
94
+
95
+ docling options (factory and per call):
96
+
97
+ | option | default | effect |
98
+ |---|---|---|
99
+ | `headingHierarchy` | `true` | heading levels by bookmarks, numbering and font; `false` = every heading level 1 |
100
+ | `formulaEnrichment` | `true` | formulas as LaTeX (extra model; runs only on formulas); without it formula `text` is empty and the md shows `<!-- formula-not-decoded -->` |
101
+ | `codeEnrichment` | `false` | code listings via extra model (slower) |
102
+ | `chartExtraction` | `false` | charts as data (vision model, slower) |
103
+ | `pipeline` | `'standard'` | `'vlm'` runs a vision-language model per page (slow) |
104
+ | `pdfBackend` | `'docling_parse'` | text extractor; `'pypdfium2'` reads ligatures and word spaces better but misses page headers and footers |
105
+ | `ocr` | `true` | OCR bitmap content of PDFs (scans, pictures); image input is always OCRed |
106
+ | `forceOcr` | `false` | OCR whole PDF pages and drop their text layer, for PDFs with broken text; ignored for image input |
107
+
108
+ Factory only (`options` of `createDoclingClient`; per call they throw `Option "x" is not supported`). Use a second
109
+ client for other values:
110
+
111
+ | option | default | effect |
112
+ |---|---|---|
113
+ | `ocrLang` | engine default | BCP-47 hints in order of preference, e.g. `['de', 'en-US']`; `[]` = engine default |
114
+ | `pageRange` | all pages | `[from, to]`, 1-based, inclusive |
115
+ | `documentTimeout` | server default | server-side limit in seconds |
116
+
117
+ Per call only: `signal` (cancels the request), `images`, for Markdown `imageOptions`, for documents `pageImages`.
118
+ `timeoutMs` is a factory setting: it limits every request except `GET /version` and is combined with `signal`.
119
+
120
+ Always on, not configurable: table structure with the accurate TableFormer model (`do_table_structure`,
121
+ `table_mode: 'accurate'`), pictures and page images at 144 dpi (`images_scale=2`, exported as `PAGE_IMAGE_SCALE`)
122
+ and, for Markdown, compact tables (`md_compact_tables`). The image scale does not affect OCR: docling OCRs at its own
123
+ fixed 216 dpi.
124
+
125
+ ### Input formats
126
+
127
+ The format is detected from the first bytes; anything else resolves to `invalid_input` without a request.
128
+
129
+ | input | detected by | sent as |
130
+ |---|---|---|
131
+ | PDF | `%PDF-` within the first 1024 bytes | `from_formats=pdf`, `document.pdf` |
132
+ | PNG, JPEG, TIFF, WebP | file signature at byte 0 | `from_formats=image`, `document.png` / `.jpg` / `.tiff` / `.webp` |
133
+
134
+ An image is converted as one page: docling runs layout detection and OCR on it, and `ocrLang` applies. OCR is always
135
+ on for images and never full-page: `ocr: false` would return no text, and `forceOcr: true` merges words (`stepsand`),
136
+ so both are ignored for image input. Use it e.g. to re-read a crop of a page whose text layer is broken. Text only
137
+ comes back if the server has a working OCR engine. PDF-only options (`pdfBackend`, `pageRange`) have no effect on
138
+ images.
139
+
140
+ ### Images in the DoclingDocument
141
+
142
+ | `images` | effect |
143
+ |---|---|
144
+ | `'embedded'` (default) | `pictures[].image.uri` is a `data:image/png;base64,…` URI; pictures keep their position, captions and size |
145
+ | `'placeholder'` | `pictures[].image` is `null`; the picture items stay, the document is much smaller |
146
+
147
+ The base64 data stays inside the document. Pull it out when you need files, e.g. when rendering Markdown from the
148
+ document. `isSmallImage(picture.image.size)` applies the same size filter as `omitSmall` (see below).
149
+
150
+ ### Page images
151
+
152
+ `pageImages: true` (documents only, default `false`) asks docling to render every page (`include_page_images`). The
153
+ client moves them out of the document: `document.pages[n].image` is always `null`, so the JSON stays small, and the
154
+ images come back decoded in `data.pageImages`, in page order:
155
+
156
+ ```ts
157
+ const res = await docling.convertDocument(pdf, { pageImages: true, images: 'placeholder' });
158
+ if (res.ok) {
159
+ await writeFile(`out/${docId}.json`, JSON.stringify(res.data.document)); // no page image data inside
160
+ for (const image of res.data.pageImages) {
161
+ await writeFile(`out/${docId}-p${image.page}.png`, image.bytes); // { page, mimeType, width, height, dpi, bytes }
162
+ }
163
+ }
164
+ ```
165
+
166
+ - `page` is the 1-based PDF page (absolute, also with `pageRange`); `dpi` is always 144.
167
+ - `width` and `height` are pixels from the PNG header, else docling's `image.size`. Pixel size = page size in points
168
+ × 2; an A4 page is about 1190 × 1684 px and 0.4 to 0.6 MB as PNG.
169
+ - Page images are independent of `images`: `images: 'placeholder'` drops picture data and keeps page images.
170
+ - Async: `submitDocument(pdf, { pageImages: true })` stores nothing extra in the job; `check()` returns the images.
171
+ - A page image that is not a base64 data URI or has invalid or empty data is missing from `pageImages`, stays in
172
+ `document.pages[].image` as docling sent it, and is reported as a `page_image_not_decoded` warning (`details.page`).
173
+
174
+ The document keeps docling's bboxes as sent: PDF points, mostly with a bottom-left origin. `toPageImageBox` maps one
175
+ onto its page image as `{ page, x, y, width, height }` in pixels with a top-left origin (unrounded), ready to store
176
+ next to the page image file or to crop with:
177
+
178
+ ```ts
179
+ for (const item of res.data.document.texts) {
180
+ for (const prov of item.prov) {
181
+ const box = toPageImageBox(res.data.document, prov); // undefined if the page is missing
182
+ }
183
+ }
184
+ // table cells carry their own bbox (top-left origin) and the page of their table:
185
+ const box = toPageImageBox(document, { page_no: table.prov[0].page_no, bbox: cell.bbox });
186
+ ```
187
+
188
+ The boxes fit page images of any conversion by this client, also when `pageImages` was off.
189
+
190
+ ### Images in the Markdown
191
+
192
+ | `images` | effect |
193
+ |---|---|
194
+ | `'placeholder'` (default) | `<!-- image -->` marks a picture, as docling writes it |
195
+ | `'none'` | placeholders removed |
196
+ | `'extract'` | pictures come back as bytes in `data.images`, linked as `<prefix><n>.<ext>` |
197
+
198
+ With `'extract'`, `imageOptions` is required on each call, because the prefix names one document's images:
199
+
200
+ ```ts
201
+ const res = await docling.convertMarkdown(pdf, {
202
+ images: 'extract',
203
+ imageOptions: { prefix: `images/${docId}-` }, // omitSmall: drop bullets, icons and small logos, default true
204
+ });
205
+ if (res.ok) {
206
+ await writeFile(`out/${docId}.md`, res.data.markdown); // ![Image](images/1234-1.png)
207
+ for (const image of res.data.images) {
208
+ await writeFile(`out/${image.path}`, image.bytes); // { path, mimeType, bytes, width, height }
209
+ }
210
+ }
211
+ ```
212
+
213
+ - `n` starts at 1 and counts kept images only. `<ext>` comes from the MIME type: `png`, `jpg`, `gif`, `webp`, `svg`,
214
+ `tiff`, `bmp`, otherwise `bin`.
215
+ - `omitSmall` (default `true`) removes small pictures from the Markdown and does not return them. Small means: area
216
+ below 15 000 px, longer side below 200 px, or shorter side below 24 px (`isSmallImage`). Only PNGs have a known
217
+ size (`readPngSize`); other formats are always kept and report `width` and `height` as `0`.
218
+ - A picture with invalid or empty base64 data is removed and reported as an `image_not_decoded` warning
219
+ (`details.index`: 1-based position among all embedded pictures); the conversion still succeeds.
220
+ - A picture docling sent without image data keeps its `<!-- image -->` placeholder, as in `'placeholder'` mode.
221
+ - Removing a picture or placeholder keeps the blank line that separates the paragraphs around it.
222
+
223
+ ### Warnings
224
+
225
+ `data.warnings` lists non-fatal notes. The conversion still succeeded; decide yourself whether a warning matters.
226
+
227
+ | code | `details` | meaning |
228
+ |---|---|---|
229
+ | `server` | `{ component, module, category, page }` | problem reported by docling-serve (e.g. a single page failed, `document timeout exceeded`); `page` is the 1-based PDF page, or `null` |
230
+ | `image_not_decoded` | `{ index }` | Markdown with `images: 'extract'` only, see above |
231
+ | `page_image_not_decoded` | `{ page }` | documents with `pageImages: true` only, see above |
232
+
233
+ Pages docling gave up on (e.g. `documentTimeout` exceeded) have no content. The only sign is a `server` warning per
234
+ failed page.
235
+
236
+ ### Server versions
237
+
238
+ `data.versions` holds the versions the server reports on `GET /version`, unchanged, e.g.
239
+ `{ 'docling-serve': '1.36.0', docling: '2.135.0', 'docling-core': '2.101.1', … }`, or `null` if it reports none.
240
+ Store it with each converted document: the same PDF can convert differently under another docling version, and
241
+ `document.version` is the DoclingDocument schema version. Errors from a request the server answered (`http`,
242
+ `not_found`, `conversion_failed`, `invalid_response`) carry the same `error.versions`. Each client asks for the
243
+ versions once and keeps them; the request has its own 2 s limit, and when it fails `versions` is `null` and the next
244
+ result asks again. Create a new client after a server update.
245
+
246
+ ### Broken umlauts / special characters
247
+
248
+ If the output shows `fŸhrt`, `Nu§baum`, `…sterreich`, the PDF's text layer uses an encoding docling-parse misreads
249
+ (seen with `/MacRomanEncoding` PDFs from Mac Word). Re-convert that document with `{ forceOcr: true }` (with `ocrLang: ['de']` on the factory).
250
+ It is slower, so use it per document, not by default.
251
+
252
+ ### Errors
253
+
254
+ `error.code` is one of:
255
+
256
+ | code | meaning |
257
+ |---|---|
258
+ | `invalid_input` | not a non-empty PDF, PNG, JPEG, TIFF or WebP `Uint8Array`, a truncated PDF (cross-reference offset past the end of the file), or not a job object from `submit*()`, or input that could not be prepared for upload (e.g. out of memory); no request sent |
259
+ | `network` | server unreachable (the message includes the cause, e.g. `ECONNREFUSED`) |
260
+ | `aborted` | your `signal` fired (incl. `AbortSignal.timeout()`) or `timeoutMs` expired |
261
+ | `http` | non-2xx response (`error.status` set), e.g. auth, sync wait exceeded |
262
+ | `not_found` | `check()`: `Unknown job id` (e.g. after a server restart), or `Result already fetched or expired`; the server's own message follows in parentheses |
263
+ | `conversion_failed` | the server could not convert the document, also a `partial_success` (or a success with error items) that has no Markdown or DoclingDocument |
264
+ | `invalid_response` | unexpected response body (no status, no Markdown or DoclingDocument without server details, a non-JSON body (quoted in the message), an unusable task id, or a body too large to read) |
265
+
266
+ `conversion_failed` adds `error.details` as `{ errors, failure }`: `errors` lists docling's error items as
267
+ `{ message, details }` (component, module, category, page); `failure` is docling's task classification
268
+ `{ category, phase, retryable }` for async jobs, or `null`.
269
+
270
+ ## Development
271
+
272
+ ### Running docling-serve (CUDA)
273
+
274
+ ```bash
275
+ # Install or reinstall docling-serve with CUDA 12.6
276
+ uv tool install --reinstall --torch-backend cu126 docling-serve
277
+
278
+ # Start the server on port 5001
279
+ docling-serve run --port 5001
280
+ ```
281
+
282
+ ```bash
283
+ bun install
284
+ bun runtest check -p tsconfig.test.json # lint fixes, typecheck, lint, unit tests against a mock docling server
285
+ bun run test:e2e # end-to-end tests against docling-serve on :5001; fail if it is down
286
+ bun run check # lint, tests, build, smoke tests on the built package, package check
287
+ ```
@@ -0,0 +1,11 @@
1
+ import type { DoclingClient, DoclingConfig } from './types.js';
2
+ /**
3
+ * **docling-serve client factory**.
4
+ *
5
+ * - validates configuration once; its only state is the cached server versions
6
+ * - returned functions resolve to `Result`; invalid options reject with a `TypeError` before any request
7
+ * - `timeoutMs` limits every request except `/version`, which has its own limit
8
+ * @throws `TypeError` on invalid configuration.
9
+ */
10
+ export declare function createDoclingClient(config: DoclingConfig): DoclingClient;
11
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAKR,aAAa,EACb,aAAa,EAOhB,MAAM,YAAY,CAAC;AAyDpB;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CA+NxE"}
@@ -0,0 +1,12 @@
1
+ import type { Result } from '../types.js';
2
+ /**
3
+ * **Executes `fetch` and parses the JSON body into a `Result`**.
4
+ *
5
+ * - non-2xx: `http` with FastAPI `detail`; unparsable 2xx body: `invalid_response`
6
+ * - body too large for a string or for memory: `invalid_response`, as a retry fails the same way
7
+ * - aborted signal: `aborted`; anything else thrown: `network`
8
+ */
9
+ export declare function fetchJson(fetchFn: typeof fetch, url: string, init: RequestInit): Promise<Result<unknown>>;
10
+ /** Extracts message, appending `cause.message` if present. */
11
+ export declare function errorMessage(e: unknown): string;
12
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAuB1C;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAmB/G;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAK/C"}
@@ -0,0 +1,18 @@
1
+ /** Detected input format and how it is sent to docling-serve. */
2
+ export type InputFormat = {
3
+ /** docling's `from_formats` value. */
4
+ fromFormat: 'pdf' | 'image';
5
+ /** MIME type of the multipart file part. */
6
+ mimeType: string;
7
+ /** File name of the multipart file part; docling reads the format from its extension. */
8
+ fileName: string;
9
+ };
10
+ /**
11
+ * **Detects the input format by magic bytes**.
12
+ *
13
+ * - image signatures must start at byte 0; PDF: `%PDF-` within the first 1024 bytes
14
+ * - images are checked first, so an image whose data happens to contain `%PDF-` is still an image
15
+ * @returns `undefined` for empty, non-`Uint8Array` or unrecognized input
16
+ */
17
+ export declare function detectInputFormat(bytes: unknown): InputFormat | undefined;
18
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/core/input.ts"],"names":[],"mappings":"AASA,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG;IACtB,sCAAsC;IACtC,UAAU,EAAE,KAAK,GAAG,OAAO,CAAC;IAC5B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAuCF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAWzE"}
@@ -0,0 +1,11 @@
1
+ /** Matches a `%PDF-` header starting within the first 1024 bytes. */
2
+ export declare function isPdf(bytes: unknown): bytes is Uint8Array;
3
+ /**
4
+ * **Returns cross-reference offset if PDF is truncated**: last 'startxref' points at or past EOF.
5
+ *
6
+ * - typical for aborted downloads; docling then fails with a vague load error
7
+ * - only the last 2 KB are read, as latin1
8
+ * - returns 'undefined' when offset lies inside file or no 'startxref' found
9
+ */
10
+ export declare function findTruncatedXref(bytes: Uint8Array): number | undefined;
11
+ //# sourceMappingURL=pdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.d.ts","sourceRoot":"","sources":["../../src/core/pdf.ts"],"names":[],"mappings":"AAoBA,qEAAqE;AACrE,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAkBzD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAKvE"}
@@ -0,0 +1,54 @@
1
+ import type { ConversionFailure, ConvertError, Result } from '../types.js';
2
+ /** Error code union inferred from `ERR`. */
3
+ export type ErrorCode = ReturnType<(typeof ERR)[keyof typeof ERR]>['code'];
4
+ /** Error factories keyed by domain error code. */
5
+ export declare const ERR: {
6
+ /** Input rejected before request: not a valid PDF `Uint8Array`, or invalid job object. */
7
+ INVALID_INPUT: (message: string) => {
8
+ code: 'invalid_input';
9
+ message: string;
10
+ };
11
+ /** Server unreachable (connection refused, DNS, socket reset). */
12
+ NETWORK: (message: string) => {
13
+ code: 'network';
14
+ message: string;
15
+ };
16
+ /** Caller `AbortSignal` or `timeoutMs` fired (including `AbortSignal.timeout()`). */
17
+ ABORTED: (message: string) => {
18
+ code: 'aborted';
19
+ message: string;
20
+ };
21
+ /** Non-2xx response (bad options, auth, 5xx, sync wait exceeded). */
22
+ HTTP: (status: number, detail: string) => {
23
+ code: 'http';
24
+ message: string;
25
+ status: number;
26
+ };
27
+ /** Job ID unknown to server: expired, restarted, or nonexistent. */
28
+ NOT_FOUND: (message: string) => {
29
+ code: 'not_found';
30
+ message: string;
31
+ status: number;
32
+ };
33
+ /** Document processing failed (corrupt PDF, model failure); `details` holds what the server reported. */
34
+ CONVERSION_FAILED: (reason: string, details: ConversionFailure) => {
35
+ code: 'conversion_failed';
36
+ message: string;
37
+ details: ConversionFailure;
38
+ };
39
+ /** Server answered 2xx with unexpected response body. */
40
+ INVALID_RESPONSE: (message: string, status?: number) => {
41
+ code: 'invalid_response';
42
+ message: string;
43
+ status?: never;
44
+ } | {
45
+ code: 'invalid_response';
46
+ message: string;
47
+ status: number;
48
+ };
49
+ };
50
+ /** Wraps value in `{ ok: true, data }`. */
51
+ export declare function ok<T>(data: T): Result<T>;
52
+ /** Wraps error in `{ ok: false, error }`. */
53
+ export declare function fail<T = never>(error: ConvertError): Result<T>;
54
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/core/result.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM3E,4CAA4C;AAC5C,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAM3E,kDAAkD;AAClD,eAAO,MAAM,GAAG;IACZ,0FAA0F;6BACjE,MAAM;cAAc,eAAe;;;IAC5D,kEAAkE;uBAC/C,MAAM;cAAc,SAAS;;;IAChD,qFAAqF;uBAClE,MAAM;cAAc,SAAS;;;IAChD,qEAAqE;mBACtD,MAAM,UAAU,MAAM;cAAc,MAAM;;;;IACzD,oEAAoE;yBAC/C,MAAM;cAAc,WAAW;;;;IACpD,yGAAyG;gCAC7E,MAAM,WAAW,iBAAiB;cACpD,mBAAmB;;;;IAI7B,yDAAyD;gCAC7B,MAAM,WAAW,MAAM;cAEjC,kBAAkB;;;;cAClB,kBAAkB;;;;CACvC,CAAC;AAMF,2CAA2C;AAC3C,wBAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAExC;AAED,6CAA6C;AAC7C,wBAAgB,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,CAE9D"}
@@ -0,0 +1,29 @@
1
+ /** Accepts or rejects one option value. */
2
+ export type OptionRule = (value: unknown) => boolean;
3
+ /** Error message when options argument is provided but is not a plain object. */
4
+ export declare const OPTIONS_OBJECT_EXPECTED = "Invalid options: expected an object";
5
+ /**
6
+ * **Checks every key of `input` against its rule**.
7
+ *
8
+ * - unsupported options throw `TypeError` even if their value is `undefined`
9
+ * - `undefined` values for supported options are skipped, as if the key were missing
10
+ * - arrays are copied, so later changes by the caller do not reach the resolved options
11
+ * - `namePrefix` names nested options in messages, e.g. `imageOptions.`
12
+ * @throws `TypeError` on unsupported option or invalid value.
13
+ */
14
+ export declare function validateKeys<T extends object>(input: Record<string, unknown>, rules: Record<keyof T, OptionRule>, namePrefix?: string): Partial<T>;
15
+ /** Formats error message for an unsupported option. */
16
+ export declare function notSupported(name: string): string;
17
+ /** Formats error message for a rejected option value. */
18
+ export declare function invalidValue(name: string): string;
19
+ /**
20
+ * **Object literal, `JSON.parse` result or `Object.create(null)`**.
21
+ *
22
+ * - rejects arrays, `Map`, `Date`, class instances and objects from another realm
23
+ */
24
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
25
+ /** Checks whether value is a boolean primitive. */
26
+ export declare function isBoolean(value: unknown): value is boolean;
27
+ /** Type guard checking whether string is included in allowed set. */
28
+ export declare function isOneOf<T extends string>(value: unknown, allowed: readonly T[]): value is T;
29
+ //# sourceMappingURL=validate.d.ts.map