docling-serve-client 0.5.0 → 0.6.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/src/images.ts CHANGED
@@ -113,7 +113,6 @@ export function readPngSize(bytes: Uint8Array): ImageSize | undefined {
113
113
  if (!(bytes instanceof Uint8Array) || bytes.length < 24) {
114
114
  return undefined;
115
115
  }
116
- // IHDR holds 32-bit width and height at offsets 16 and 20.
117
116
  const isPng =
118
117
  PNG_SIGNATURE.every((byte, i) => bytes[i] === byte) && PNG_IHDR.every((byte, i) => bytes[12 + i] === byte);
119
118
  if (!isPng) {
@@ -154,7 +153,6 @@ export function extractImages(markdown: string, options: Required<ImageOptions>)
154
153
  }
155
154
  const size = readPngSize(bytes);
156
155
  if (options.omitSmall && size !== undefined && isSmallImage(size)) {
157
- // Marker, so removing it below also normalizes surrounding blank lines.
158
156
  output += OMITTED_IMAGE;
159
157
  continue;
160
158
  }
@@ -263,7 +261,7 @@ export function removeImagePlaceholders(markdown: string): string {
263
261
  }
264
262
 
265
263
  // -------------------------------------------------------------------
266
- // --- 4. Internal Functions
264
+ // --- 4. Private Functions
267
265
  // -------------------------------------------------------------------
268
266
 
269
267
  /**
package/src/index.ts CHANGED
@@ -45,6 +45,7 @@ export type {
45
45
  PdfBackend,
46
46
  PdfInput,
47
47
  Pipeline,
48
+ PollOptions,
48
49
  Result,
49
50
  ServerCapabilities,
50
51
  ServerDetails,
package/src/options.ts CHANGED
@@ -23,6 +23,7 @@ import type {
23
23
  MarkdownImageMode,
24
24
  MarkdownJobOutput,
25
25
  MarkdownOptions,
26
+ PollOptions,
26
27
  Result,
27
28
  } from './types.js';
28
29
 
@@ -44,10 +45,18 @@ export type ResolvedConfig = {
44
45
  fetch: typeof fetch;
45
46
  /** Time limit per request in ms; unset means no limit. */
46
47
  timeoutMs?: number;
48
+ /** Delay between job helper status polls in ms; defaults to `DEFAULT_POLL_MS`. */
49
+ pollMs: number;
47
50
  /** Factory defaults merged over `DEFAULT_OPTIONS`. */
48
51
  defaults: ResolvedOptions;
49
52
  };
50
53
 
54
+ /** Validated poll settings for job helpers. */
55
+ export type PollCall = {
56
+ pollMs: number;
57
+ signal?: AbortSignal;
58
+ };
59
+
51
60
  /** Validated md call: server options, image handling and signal. */
52
61
  export type MarkdownCall = {
53
62
  options: ResolvedOptions;
@@ -102,7 +111,6 @@ const CALL_OPTION_RULES: Record<keyof DoclingOptions, OptionRule> = {
102
111
  pdfBackend: (value) => isOneOf(value, PDF_BACKENDS),
103
112
  ocr: isBoolean,
104
113
  forceOcr: isBoolean,
105
- // Open list: presets depend on server installation, unknown IDs are left to server.
106
114
  ocrEngine: (value) => typeof value === 'string' && value !== '',
107
115
  };
108
116
 
@@ -141,15 +149,37 @@ const CHECK_RULES: Record<keyof CallOptions, OptionRule> = {
141
149
  signal: isAbortSignal,
142
150
  };
143
151
 
152
+ /** Rules for `waitForJob` options. */
153
+ const POLL_RULES: Record<keyof PollOptions, OptionRule> = {
154
+ ...CHECK_RULES,
155
+ pollMs: isTimerDelay,
156
+ };
157
+
158
+ /** Rules for `runMarkdown` options: md call options plus `pollMs`. */
159
+ const RUN_MARKDOWN_RULES: Record<keyof (MarkdownOptions & PollOptions), OptionRule> = {
160
+ ...MARKDOWN_RULES,
161
+ ...POLL_RULES,
162
+ };
163
+
164
+ /** Rules for `runDocument` options: document call options plus `pollMs`. */
165
+ const RUN_DOCUMENT_RULES: Record<keyof (DocumentOptions & PollOptions), OptionRule> = {
166
+ ...DOCUMENT_RULES,
167
+ ...POLL_RULES,
168
+ };
169
+
144
170
  /** Rules for factory configuration; the keys of `options` are checked separately. */
145
171
  const CONFIG_RULES: Record<keyof DoclingConfig, OptionRule> = {
146
172
  baseUrl: isHttpUrl,
147
173
  apiKey: (value) => typeof value === 'string' && value !== '',
148
174
  fetch: (value) => typeof value === 'function',
149
- timeoutMs: isPositiveNumber,
175
+ timeoutMs: isTimerDelay,
176
+ pollMs: isTimerDelay,
150
177
  options: isPlainObject,
151
178
  };
152
179
 
180
+ /** Default delay in ms between job helper status polls. */
181
+ const DEFAULT_POLL_MS = 10;
182
+
153
183
  /** Job id of word characters and `-` only, so it cannot leave the URL path segment. */
154
184
  const MATCH_JOB_ID = /^[\w-]+$/;
155
185
 
@@ -165,6 +195,9 @@ const MATCH_TRAILING_SLASHES = /\/+$/;
165
195
  /** Matches HTTP or HTTPS protocol schemes. */
166
196
  const MATCH_HTTP_PROTOCOL = /^https?:$/;
167
197
 
198
+ /** Maximum timer delay in ms (2^31 - 1); Node timers overflow above this and fire after 1 ms. */
199
+ const MAX_TIMEOUT_MS = 2_147_483_647;
200
+
168
201
  /** Error message for a missing or malformed job object. */
169
202
  const INVALID_JOB = 'Expected the job object returned by submitMarkdown() or submitDocument()';
170
203
 
@@ -182,7 +215,14 @@ export function resolveConfig(config: unknown): ResolvedConfig {
182
215
  if (!isPlainObject(config)) {
183
216
  throw new TypeError('Invalid config: expected an object');
184
217
  }
185
- const { baseUrl, apiKey, fetch: fetchFn, timeoutMs, options } = validateKeys<DoclingConfig>(config, CONFIG_RULES);
218
+ const {
219
+ baseUrl,
220
+ apiKey,
221
+ fetch: fetchFn,
222
+ timeoutMs,
223
+ pollMs = DEFAULT_POLL_MS,
224
+ options,
225
+ } = validateKeys<DoclingConfig>(config, CONFIG_RULES);
186
226
  if (baseUrl === undefined) {
187
227
  throw new TypeError(invalidValue('baseUrl'));
188
228
  }
@@ -195,47 +235,76 @@ export function resolveConfig(config: unknown): ResolvedConfig {
195
235
  ...(apiKey === undefined ? {} : { apiKey }),
196
236
  fetch: fetchFn ?? fetch,
197
237
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
238
+ pollMs,
198
239
  defaults,
199
240
  };
200
241
  }
201
242
 
202
243
  /**
203
- * Validates md call options over factory defaults.
244
+ * Merges and validates md call options against factory defaults.
204
245
  * @throws `TypeError` on invalid options.
205
246
  */
206
247
  export function resolveMarkdownCall(defaults: Readonly<ResolvedOptions>, options: unknown): MarkdownCall {
207
- const {
208
- signal,
209
- images = 'placeholder',
210
- imageOptions,
211
- ...docling
212
- } = validateKeys<MarkdownOptions>(callObject(options), MARKDOWN_RULES);
213
- const output = resolveImageOutput(images, imageOptions);
214
- return { options: { ...defaults, ...docling }, output, ...(signal === undefined ? {} : { signal }) };
248
+ return toMarkdownCall(defaults, validateKeys<MarkdownOptions>(callObject(options), MARKDOWN_RULES));
215
249
  }
216
250
 
217
251
  /**
218
- * Validates document call options over factory defaults.
252
+ * Merges and validates document call options against factory defaults.
219
253
  * @throws `TypeError` on invalid options.
220
254
  */
221
255
  export function resolveDocumentCall(defaults: Readonly<ResolvedOptions>, options: unknown): DocumentCall {
222
- const {
223
- signal,
224
- images = 'embedded',
225
- pageImages = false,
226
- ...docling
227
- } = validateKeys<DocumentOptions>(callObject(options), DOCUMENT_RULES);
228
- return { options: { ...defaults, ...docling }, images, pageImages, ...(signal === undefined ? {} : { signal }) };
256
+ return toDocumentCall(defaults, validateKeys<DocumentOptions>(callObject(options), DOCUMENT_RULES));
257
+ }
258
+
259
+ /**
260
+ * Merges md call options and poll settings; `pollMs` defaults to factory `pollMs`.
261
+ * @throws `TypeError` on invalid options.
262
+ */
263
+ export function resolveRunMarkdownCall(
264
+ defaults: Readonly<ResolvedOptions>,
265
+ defaultPollMs: number,
266
+ options: unknown,
267
+ ): MarkdownCall & PollCall {
268
+ const { pollMs = defaultPollMs, ...call } = validateKeys<MarkdownOptions & PollOptions>(
269
+ callObject(options),
270
+ RUN_MARKDOWN_RULES,
271
+ );
272
+ return { ...toMarkdownCall(defaults, call), pollMs };
273
+ }
274
+
275
+ /**
276
+ * Merges document call options and poll settings; `pollMs` defaults to factory `pollMs`.
277
+ * @throws `TypeError` on invalid options.
278
+ */
279
+ export function resolveRunDocumentCall(
280
+ defaults: Readonly<ResolvedOptions>,
281
+ defaultPollMs: number,
282
+ options: unknown,
283
+ ): DocumentCall & PollCall {
284
+ const { pollMs = defaultPollMs, ...call } = validateKeys<DocumentOptions & PollOptions>(
285
+ callObject(options),
286
+ RUN_DOCUMENT_RULES,
287
+ );
288
+ return { ...toDocumentCall(defaults, call), pollMs };
229
289
  }
230
290
 
231
291
  /**
232
- * Validates signal-only call options (`check`, `capabilities`).
292
+ * Checks signal-only call options (`check`, `capabilities`).
233
293
  * @throws `TypeError` on invalid options.
234
294
  */
235
295
  export function resolveCheckOptions(options: unknown): CallOptions {
236
296
  return validateKeys<CallOptions>(callObject(options), CHECK_RULES);
237
297
  }
238
298
 
299
+ /**
300
+ * Checks `waitForJob` options; `pollMs` defaults to factory `pollMs`.
301
+ * @throws `TypeError` on invalid options.
302
+ */
303
+ export function resolvePollOptions(defaultPollMs: number, options: unknown): PollCall {
304
+ const { pollMs = defaultPollMs, signal } = validateKeys<PollOptions>(callObject(options), POLL_RULES);
305
+ return { pollMs, ...(signal === undefined ? {} : { signal }) };
306
+ }
307
+
239
308
  /**
240
309
  * **Checks a job object against the shape `submitMarkdown()` and `submitDocument()` return**.
241
310
  *
@@ -269,7 +338,7 @@ export function isJobId(value: unknown): value is string {
269
338
  }
270
339
 
271
340
  // -------------------------------------------------------------------
272
- // --- 4. Internal Functions
341
+ // --- 4. Private Functions
273
342
  // -------------------------------------------------------------------
274
343
 
275
344
  /**
@@ -286,6 +355,22 @@ function callObject(options: unknown): Record<string, unknown> {
286
355
  return options;
287
356
  }
288
357
 
358
+ /**
359
+ * Splits validated md call options into server options over the defaults, image handling and signal.
360
+ * @throws `TypeError` on a missing, unexpected or invalid `imageOptions`.
361
+ */
362
+ function toMarkdownCall(defaults: Readonly<ResolvedOptions>, options: Partial<MarkdownOptions>): MarkdownCall {
363
+ const { signal, images = 'placeholder', imageOptions, ...docling } = options;
364
+ const output = resolveImageOutput(images, imageOptions);
365
+ return { options: { ...defaults, ...docling }, output, ...(signal === undefined ? {} : { signal }) };
366
+ }
367
+
368
+ /** Splits validated document call options into server options over the defaults, image modes and signal. */
369
+ function toDocumentCall(defaults: Readonly<ResolvedOptions>, options: Partial<DocumentOptions>): DocumentCall {
370
+ const { signal, images = 'embedded', pageImages = false, ...docling } = options;
371
+ return { options: { ...defaults, ...docling }, images, pageImages, ...(signal === undefined ? {} : { signal }) };
372
+ }
373
+
289
374
  /**
290
375
  * **Combines image mode and image options into the md job output**.
291
376
  *
@@ -317,6 +402,11 @@ function isPositiveNumber(value: unknown): value is number {
317
402
  return typeof value === 'number' && Number.isFinite(value) && value > 0;
318
403
  }
319
404
 
405
+ /** Integer ms from 1 to `MAX_TIMEOUT_MS`; `AbortSignal.timeout` throws on fractions, `setTimeout` overflows above. */
406
+ function isTimerDelay(value: unknown): value is number {
407
+ return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= MAX_TIMEOUT_MS;
408
+ }
409
+
320
410
  /** 1-based ascending integer pair `[from, to]`. */
321
411
  function isPageRange(value: unknown): value is [number, number] {
322
412
  return (
@@ -344,7 +434,16 @@ function isAbortSignal(value: unknown): value is AbortSignal {
344
434
  return value instanceof AbortSignal;
345
435
  }
346
436
 
347
- /** Parseable HTTP or HTTPS URL. */
437
+ /**
438
+ * **HTTP or HTTPS URL that routes can be appended to**.
439
+ *
440
+ * - no `?` or `#`, even empty ones: `${baseUrl}${route}` would put the route into the query or fragment
441
+ * - no user name or password: Node's `fetch` refuses such URLs; the key belongs in `apiKey`
442
+ */
348
443
  function isHttpUrl(value: unknown): value is string {
349
- return typeof value === 'string' && URL.canParse(value) && MATCH_HTTP_PROTOCOL.test(new URL(value).protocol);
444
+ if (typeof value !== 'string' || !URL.canParse(value) || value.includes('?') || value.includes('#')) {
445
+ return false;
446
+ }
447
+ const url = new URL(value);
448
+ return MATCH_HTTP_PROTOCOL.test(url.protocol) && url.username === '' && url.password === '';
350
449
  }
package/src/response.ts CHANGED
@@ -135,7 +135,7 @@ export function readText(value: unknown): string | undefined {
135
135
  }
136
136
 
137
137
  // -------------------------------------------------------------------
138
- // --- 4. Internal Functions
138
+ // --- 4. Private Functions
139
139
  // -------------------------------------------------------------------
140
140
 
141
141
  /**
package/src/types.ts CHANGED
@@ -5,7 +5,7 @@ import type { ErrorCode } from './core/result.js';
5
5
  import type { DoclingDocument } from './document.js';
6
6
 
7
7
  // -------------------------------------------------------------------
8
- // --- 1. Result & Error Types
8
+ // --- 1. Types
9
9
  // -------------------------------------------------------------------
10
10
 
11
11
  /** Identifies failure cause across validation, network, HTTP, and conversion. */
@@ -90,9 +90,7 @@ export type ServerError = {
90
90
  */
91
91
  export type ServerVersions = Record<string, string>;
92
92
 
93
- // -------------------------------------------------------------------
94
- // --- 2. Options
95
- // -------------------------------------------------------------------
93
+ // Options
96
94
 
97
95
  /**
98
96
  * **Bytes of the file to convert; the format is detected by magic bytes**.
@@ -103,10 +101,7 @@ export type ServerVersions = Record<string, string>;
103
101
  */
104
102
  export type ConvertInput = Uint8Array;
105
103
 
106
- /**
107
- * Use `ConvertInput`; images are accepted as well.
108
- * @deprecated
109
- */
104
+ /** Deprecated: use `ConvertInput`; images are accepted as well. */
110
105
  export type PdfInput = ConvertInput;
111
106
 
112
107
  /** Supported conversion pipeline backend. */
@@ -146,6 +141,7 @@ export type DoclingOptions = {
146
141
  /**
147
142
  * **OCRs whole PDF pages and drops their text layer**: for PDFs with broken text. Default `false`.
148
143
  *
144
+ * - enables OCR even with `ocr: false` (docling skips full-page OCR when `do_ocr=false`)
149
145
  * - ignored for image input: it has no text layer, and full-page OCR there merges words (`stepsand`)
150
146
  */
151
147
  forceOcr?: boolean;
@@ -169,6 +165,17 @@ export type CallOptions = {
169
165
  signal?: AbortSignal;
170
166
  };
171
167
 
168
+ /**
169
+ * **Options for job helpers (`runMarkdown`, `runDocument`, `waitForJob`)**.
170
+ *
171
+ * - `signal` covers entire call: submit, status polls, and waits
172
+ * - rejected by `convert*`, `submit*`, and `check`
173
+ */
174
+ export type PollOptions = CallOptions & {
175
+ /** Delay between status polls in ms (integer `1..2147483647`). Default: factory `pollMs` (`10`). */
176
+ pollMs?: number;
177
+ };
178
+
172
179
  /**
173
180
  * **Images in the md**.
174
181
  *
@@ -213,9 +220,7 @@ export type DocumentOptions = DoclingOptions &
213
220
  pageImages?: boolean;
214
221
  };
215
222
 
216
- // -------------------------------------------------------------------
217
- // --- 3. Outputs
218
- // -------------------------------------------------------------------
223
+ // Outputs
219
224
 
220
225
  /** Pixel dimensions of an image. */
221
226
  export type ImageSize = {
@@ -359,14 +364,12 @@ export type DoclingSettings = {
359
364
  abortOnError: boolean;
360
365
  };
361
366
 
362
- /** Sync conversion result plus the settings sent with its request. */
367
+ /** Result of `convert*` and `run*` plus the settings sent with the conversion. */
363
368
  export type WithSettings<T> = T & {
364
369
  settings: DoclingSettings;
365
370
  };
366
371
 
367
- // -------------------------------------------------------------------
368
- // --- 4. Asynchronous Jobs
369
- // -------------------------------------------------------------------
372
+ // Asynchronous Jobs
370
373
 
371
374
  /** Image handling of a md job, applied when `check()` picks up the result. */
372
375
  export type MarkdownJobOutput =
@@ -407,9 +410,7 @@ export type JobState<T> =
407
410
  /** Completed; carries the conversion result. */
408
411
  | ({ status: 'done' } & T);
409
412
 
410
- // -------------------------------------------------------------------
411
- // --- 5. Server Capabilities
412
- // -------------------------------------------------------------------
413
+ // Server Capabilities
413
414
 
414
415
  /** Model or engine preset a pipeline stage offers; `id` is the value of the stage's form field. */
415
416
  export type CapabilityPreset = {
@@ -464,18 +465,18 @@ export type ServerCapabilities = {
464
465
  };
465
466
  };
466
467
 
467
- // -------------------------------------------------------------------
468
- // --- 6. Client
469
- // -------------------------------------------------------------------
468
+ // Client
470
469
 
471
470
  /** Configuration passed to `createDoclingClient`. */
472
471
  export type DoclingConfig = {
473
- /** docling-serve base URL, e.g. `'http://127.0.0.1:5001'`; trailing `/` are removed. */
472
+ /** docling-serve base URL, e.g. `'http://127.0.0.1:5001'`; trailing `/` removed; no query, fragment, or credentials. */
474
473
  baseUrl: string;
475
474
  /** Sent as `X-Api-Key` header. */
476
475
  apiKey?: string;
477
- /** Limit per request in ms (not `/version`); on expiry the call resolves with `aborted`. */
476
+ /** Time limit per request in ms (integer `1..2147483647`, excludes `/version`); resolves to `aborted` on expiry. */
478
477
  timeoutMs?: number;
478
+ /** Delay between job helper status polls in ms (integer `1..2147483647`). Default `10`. */
479
+ pollMs?: number;
479
480
  /** Custom fetch implementation for mocking or custom dispatchers. */
480
481
  fetch?: typeof fetch;
481
482
  /** Defaults for every call; call options override them key by key, except the factory-only ones. */
@@ -483,10 +484,12 @@ export type DoclingConfig = {
483
484
  };
484
485
 
485
486
  /**
486
- * **docling-serve client**.
487
+ * **docling-serve client**: every call returns a Promise.
487
488
  *
488
- * - `convert*`: one synchronous request
489
- * - `submit*` + `check`: async job; the caller stores the job and polls
489
+ * - single request (`convert*`): one HTTP request to sync endpoint; answers on `DOCLING_SERVE_SYNC_POLL_INTERVAL`
490
+ * ticks (default 2 s) and returns 504 after `DOCLING_SERVE_MAX_SYNC_WAIT`
491
+ * - job (`submit*` + `check`): async endpoints; caller stores job and polls
492
+ * - job helper (`run*`, `waitForJob`): submits or takes a job and polls every `pollMs` until done
490
493
  */
491
494
  export type DoclingClient = {
492
495
  /** PDF or image to docling's md; images as placeholders, removed or extracted. */
@@ -500,14 +503,46 @@ export type DoclingClient = {
500
503
  /**
501
504
  * **Polls a job; fetches its result once done**.
502
505
  *
503
- * - docling-serve deletes a result `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds (default 300) after its first
504
- * fetch: a `check` whose result download failed can be repeated within that time, later it returns
505
- * `not_found`; persist `done` data, or start docling-serve with `DOCLING_SERVE_SINGLE_USE_RESULTS=false`
506
- * - `done` carries no settings: they are on the job
506
+ * - docling-serve deletes the task `DOCLING_SERVE_RESULT_REMOVAL_DELAY` seconds (default 300) after first
507
+ * result fetch: a `check` whose result download failed can be repeated within that window, later returning
508
+ * `not_found` (`Unknown job id`); persist `done` data, or set `DOCLING_SERVE_SINGLE_USE_RESULTS=false`
509
+ * - `done` carries no settings (stored on the job)
507
510
  */
508
511
  check(job: Omit<MarkdownJob, 'settings'>, options?: CallOptions): Promise<Result<JobState<ConvertedMarkdown>>>;
509
512
  check(job: Omit<DocumentJob, 'settings'>, options?: CallOptions): Promise<Result<JobState<ConvertedDocument>>>;
510
513
  check(job: JobRef, options?: CallOptions): Promise<Result<JobState<ConvertedMarkdown | ConvertedDocument>>>;
514
+ /**
515
+ * **Submits an md job and polls every `pollMs` until done**: same result as `convertMarkdown`.
516
+ *
517
+ * - `signal` covers submit, status polls, and waits; `timeoutMs` limits each HTTP request
518
+ * - first error ends call; job never returned (use `submitMarkdown` + `waitForJob` to keep it)
519
+ * - aborting does not cancel the server-side job
520
+ */
521
+ runMarkdown(
522
+ input: ConvertInput,
523
+ options?: MarkdownOptions & PollOptions,
524
+ ): Promise<Result<WithSettings<ConvertedMarkdown>>>;
525
+ /**
526
+ * **Submits a document job and polls every `pollMs` until done**: same result as `convertDocument`.
527
+ *
528
+ * - `signal` covers submit, status polls, and waits; `timeoutMs` limits each HTTP request
529
+ * - first error ends call; job never returned (use `submitDocument` + `waitForJob` to keep it)
530
+ * - aborting does not cancel the server-side job
531
+ */
532
+ runDocument(
533
+ input: ConvertInput,
534
+ options?: DocumentOptions & PollOptions,
535
+ ): Promise<Result<WithSettings<ConvertedDocument>>>;
536
+ /**
537
+ * **Polls an existing job every `pollMs` until done**.
538
+ *
539
+ * - first status poll runs immediately; already-finished jobs cost one `check`
540
+ * - resolves to `check`'s `done` data without `status`; first error ends call
541
+ * - repeatable after a failed result download within `DOCLING_SERVE_RESULT_REMOVAL_DELAY` (default 300 s)
542
+ */
543
+ waitForJob(job: Omit<MarkdownJob, 'settings'>, options?: PollOptions): Promise<Result<ConvertedMarkdown>>;
544
+ waitForJob(job: Omit<DocumentJob, 'settings'>, options?: PollOptions): Promise<Result<ConvertedDocument>>;
545
+ waitForJob(job: JobRef, options?: PollOptions): Promise<Result<ConvertedMarkdown | ConvertedDocument>>;
511
546
  /** Installed presets, limits and features of the server; on demand only, never used by conversions. */
512
547
  capabilities(options?: CallOptions): Promise<Result<ServerCapabilities>>;
513
548
  };