@docmana/sdk 0.24.0 → 0.26.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/CHANGELOG.md +19 -8
- package/README.md +22 -12
- package/dist/api/multipart-request.d.ts +2 -0
- package/dist/cli.mjs +111 -46
- package/dist/cli.mjs.map +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +112 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -43
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## Unreleased
|
|
4
|
-
|
|
5
|
-
## 0.
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.26.0 - 2026-09-07
|
|
6
|
+
|
|
7
|
+
- Expose the exact numeric version that a new flow execution would use through
|
|
8
|
+
`getFlowMetadata`.
|
|
9
|
+
|
|
10
|
+
## 0.25.0 - 2026-08-24
|
|
11
|
+
|
|
12
|
+
- Stream file-path inputs from disk instead of loading them completely into memory.
|
|
13
|
+
- Expose typed `413` and `503` upload errors, preserve `Retry-After`, and retry one
|
|
14
|
+
multipart admission `503` carrying that header with a fresh request body.
|
|
15
|
+
|
|
16
|
+
## 0.24.0 - 2026-08-20
|
|
17
|
+
|
|
18
|
+
- Preserve the exact numeric flow version when mapping raw execution results.
|
|
19
|
+
- Expose each processing step's LLM model and immutable prompt identifier without
|
|
20
|
+
reintroducing token or cost telemetry.
|
|
21
|
+
- Update vulnerable HTTP and build-tool transitive dependencies to patched versions.
|
|
11
22
|
|
|
12
23
|
## 0.23.0 - 2026-08-07
|
|
13
24
|
|
package/README.md
CHANGED
|
@@ -117,7 +117,9 @@ use `RunFlowParams` for request data:
|
|
|
117
117
|
- `listOrganizations()` calls `GET /v1/organizations` and returns
|
|
118
118
|
`{ id, name }[]` for the organizations available to the Bearer token.
|
|
119
119
|
- `getFlowMetadata(flowId)` calls `GET /v1/flows/:flowId/metadata` and returns
|
|
120
|
-
`{ flowId, name?, jsonContextSchema, languages, minScore?, documents }`.
|
|
120
|
+
`{ flowId, name?, version?, jsonContextSchema, languages, minScore?, documents }`.
|
|
121
|
+
`version` is the exact published or draft flow version that a new execution
|
|
122
|
+
would use.
|
|
121
123
|
- `getFlowDefinition(flowId)` calls `GET /v1/flows/:flowId/definition` and
|
|
122
124
|
returns the normalized flow definition `{ flowId, name?, languages, minScore?, nodes }`.
|
|
123
125
|
- `listOrganizationDocuments({ prefix? })` calls
|
|
@@ -160,6 +162,12 @@ the SDK waits that many milliseconds before the next status call. If the header
|
|
|
160
162
|
is absent, the SDK falls back to capped exponential full jitter between
|
|
161
163
|
`pollIntervalMs` and `pollMaxIntervalMs`. `Retry-After` on `429` always wins.
|
|
162
164
|
|
|
165
|
+
Path inputs are streamed from disk instead of being loaded completely into memory.
|
|
166
|
+
For multipart submissions only, the SDK retries one `503 Service Unavailable`
|
|
167
|
+
response carrying `Retry-After` with a fresh request body, respecting that delay
|
|
168
|
+
up to 30 seconds. A downstream `503` without `Retry-After` is not retried.
|
|
169
|
+
Payloads rejected with `413 Payload Too Large` are never retried.
|
|
170
|
+
|
|
163
171
|
The SDK does not log or return `documentUrl` or uploaded file contents. Signed
|
|
164
172
|
query parameters are sent only in the JSON request body to Consumer Edge. Fraud
|
|
165
173
|
jobs are not organization-scoped; the legacy `organization` inputs are accepted
|
|
@@ -202,7 +210,7 @@ console.log(organizations.map((org) => `${org.id}: ${org.name}`).join("\n"));
|
|
|
202
210
|
|
|
203
211
|
```ts
|
|
204
212
|
const metadata = await docmana.getFlowMetadata("<flow-id>");
|
|
205
|
-
console.log(metadata.jsonContextSchema);
|
|
213
|
+
console.log(metadata.version, metadata.jsonContextSchema);
|
|
206
214
|
|
|
207
215
|
const definition = await docmana.getFlowDefinition("<flow-id>");
|
|
208
216
|
console.log(definition.nodes.map((node) => node.label).join("\n"));
|
|
@@ -322,16 +330,18 @@ from the Bearer token.
|
|
|
322
330
|
Every SDK error extends `DocmanaError`, which carries `.status`, `.code`, and an
|
|
323
331
|
optional `.requestId` when available.
|
|
324
332
|
|
|
325
|
-
| Error
|
|
326
|
-
|
|
|
327
|
-
| `DocmanaRequestError`
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `
|
|
333
|
+
| Error | Thrown when |
|
|
334
|
+
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
335
|
+
| `DocmanaRequestError` | `400` bad request. |
|
|
336
|
+
| `DocmanaPayloadTooLargeError` | `413` multipart payload exceeds a Consumer limit. |
|
|
337
|
+
| `DocmanaAuthError` | `401` rejected or expired Bearer token. |
|
|
338
|
+
| `DocmanaPermissionError` | `403` missing permission. |
|
|
339
|
+
| `DocmanaNotFoundError` | `404` flow or execution not found. |
|
|
340
|
+
| `DocmanaExecutionError` | The flow finished with status `"FAILED"`. Carries `.errors` and `.result`. |
|
|
341
|
+
| `DocmanaFraudDetectionError` | Fraud detection finished with status `"FAILED"`. Carries `.jobId`, `.error`, and `.result`. |
|
|
342
|
+
| `DocmanaTimeoutError` | Polling exceeded `timeoutMs`. |
|
|
343
|
+
| `DocmanaAbortError` | The provided `AbortSignal` fired. |
|
|
344
|
+
| `DocmanaServiceUnavailableError` | `503` Consumer upload capacity is unavailable after the bounded retry. Carries `.retryAfterMs`. |
|
|
335
345
|
|
|
336
346
|
## Local development with yalc
|
|
337
347
|
|
package/dist/cli.mjs
CHANGED
|
@@ -70,6 +70,11 @@ var DocmanaRequestError = class extends DocmanaError {
|
|
|
70
70
|
super(message, { ...opts, code: "bad_request" });
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
|
+
var DocmanaPayloadTooLargeError = class extends DocmanaError {
|
|
74
|
+
constructor(message, opts = {}) {
|
|
75
|
+
super(message, { ...opts, code: "payload_too_large" });
|
|
76
|
+
}
|
|
77
|
+
};
|
|
73
78
|
var DocmanaExecutionError = class extends DocmanaError {
|
|
74
79
|
errors;
|
|
75
80
|
result;
|
|
@@ -107,6 +112,13 @@ var DocmanaRateLimitError = class extends DocmanaError {
|
|
|
107
112
|
this.retryAfterMs = opts.retryAfterMs;
|
|
108
113
|
}
|
|
109
114
|
};
|
|
115
|
+
var DocmanaServiceUnavailableError = class extends DocmanaError {
|
|
116
|
+
retryAfterMs;
|
|
117
|
+
constructor(message, opts = {}) {
|
|
118
|
+
super(message, { ...opts, code: "service_unavailable" });
|
|
119
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
110
122
|
function errorFromResponse(status, message, requestId, retryAfterMs) {
|
|
111
123
|
const opts = { status, requestId };
|
|
112
124
|
switch (status) {
|
|
@@ -118,8 +130,12 @@ function errorFromResponse(status, message, requestId, retryAfterMs) {
|
|
|
118
130
|
return new DocmanaPermissionError(message, opts);
|
|
119
131
|
case 404:
|
|
120
132
|
return new DocmanaNotFoundError(message, opts);
|
|
133
|
+
case 413:
|
|
134
|
+
return new DocmanaPayloadTooLargeError(message, opts);
|
|
121
135
|
case 429:
|
|
122
136
|
return new DocmanaRateLimitError(message, { ...opts, retryAfterMs });
|
|
137
|
+
case 503:
|
|
138
|
+
return new DocmanaServiceUnavailableError(message, { ...opts, retryAfterMs });
|
|
123
139
|
default:
|
|
124
140
|
return new DocmanaError(message, opts);
|
|
125
141
|
}
|
|
@@ -215,7 +231,7 @@ function parseRetryAfterMs(value) {
|
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
// src/sdk/files/resolve-input.ts
|
|
218
|
-
import {
|
|
234
|
+
import { openAsBlob } from "fs";
|
|
219
235
|
import { basename } from "path";
|
|
220
236
|
var CONTENT_TYPES = {
|
|
221
237
|
".pdf": "application/pdf",
|
|
@@ -255,16 +271,18 @@ function toBlobBytes(bytes) {
|
|
|
255
271
|
async function resolveOne(input) {
|
|
256
272
|
if (typeof input === "string" || isPathInput(input)) {
|
|
257
273
|
const path = typeof input === "string" ? input : input.path;
|
|
258
|
-
const data = await readFile(path);
|
|
259
274
|
const filename = basename(path);
|
|
260
|
-
|
|
275
|
+
const contentType = contentTypeFor(filename);
|
|
276
|
+
const data = await openAsBlob(path, { type: contentType });
|
|
277
|
+
return { data, filename, contentType };
|
|
261
278
|
}
|
|
262
279
|
if (isNamedFileInput(input)) {
|
|
263
280
|
const bytes = input.data instanceof Uint8Array ? input.data : await streamToBuffer(input.data);
|
|
281
|
+
const contentType = input.contentType ?? contentTypeFor(input.filename);
|
|
264
282
|
return {
|
|
265
|
-
data: new Blob([toBlobBytes(bytes)]),
|
|
283
|
+
data: new Blob([toBlobBytes(bytes)], { type: contentType }),
|
|
266
284
|
filename: input.filename,
|
|
267
|
-
contentType
|
|
285
|
+
contentType
|
|
268
286
|
};
|
|
269
287
|
}
|
|
270
288
|
throw new Error("Unsupported file input type");
|
|
@@ -275,55 +293,98 @@ async function resolveInputs(input) {
|
|
|
275
293
|
return Promise.all(list.map((item) => resolveOne(item)));
|
|
276
294
|
}
|
|
277
295
|
|
|
296
|
+
// src/sdk/api/multipart-request.ts
|
|
297
|
+
var MAX_ATTEMPTS = 2;
|
|
298
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
299
|
+
async function requestMultipartJson(http, path, createForm, signal) {
|
|
300
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
301
|
+
try {
|
|
302
|
+
return await http.requestJson("POST", path, {
|
|
303
|
+
body: createForm(),
|
|
304
|
+
signal
|
|
305
|
+
});
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (!(error instanceof DocmanaServiceUnavailableError) || error.retryAfterMs === void 0 || attempt >= MAX_ATTEMPTS) {
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
await waitForRetry(Math.min(error.retryAfterMs, MAX_RETRY_DELAY_MS), signal);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function waitForRetry(delayMs, signal) {
|
|
315
|
+
if (signal?.aborted) throw new DocmanaAbortError("Upload retry aborted");
|
|
316
|
+
await new Promise((resolve3, reject) => {
|
|
317
|
+
let settled = false;
|
|
318
|
+
const finish = (error) => {
|
|
319
|
+
if (settled) return;
|
|
320
|
+
settled = true;
|
|
321
|
+
clearTimeout(timer);
|
|
322
|
+
signal?.removeEventListener("abort", onAbort);
|
|
323
|
+
if (error) reject(error);
|
|
324
|
+
else resolve3();
|
|
325
|
+
};
|
|
326
|
+
const onAbort = () => finish(new DocmanaAbortError("Upload retry aborted"));
|
|
327
|
+
const timer = setTimeout(() => finish(), delayMs);
|
|
328
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
278
332
|
// src/sdk/api/run-flow.ts
|
|
279
333
|
async function runFlow(http, flowId, parts, signal, useDraftVersion = false, context, callbackUrl, language, organization) {
|
|
280
|
-
const form = new FormData();
|
|
281
|
-
for (const part of parts) {
|
|
282
|
-
form.append("files", new File([part.data], part.filename, { type: part.contentType }));
|
|
283
|
-
}
|
|
284
|
-
if (context !== void 0) form.append("jsonContext", JSON.stringify(context));
|
|
285
|
-
if (callbackUrl !== void 0) form.append("callbackUrl", callbackUrl);
|
|
286
|
-
if (language !== void 0) form.append("language", language);
|
|
287
|
-
if (useDraftVersion) form.append("useDraftVersion", "true");
|
|
288
334
|
const path = `/flows/${flowId}/execute-async`;
|
|
289
|
-
return
|
|
290
|
-
|
|
335
|
+
return requestMultipartJson(
|
|
336
|
+
http,
|
|
291
337
|
organization ? `${path}?organization=${encodeURIComponent(organization)}` : path,
|
|
292
|
-
|
|
338
|
+
() => {
|
|
339
|
+
const form = new FormData();
|
|
340
|
+
for (const part of parts) form.append("files", part.data, part.filename);
|
|
341
|
+
if (context !== void 0) form.append("jsonContext", JSON.stringify(context));
|
|
342
|
+
if (callbackUrl !== void 0) form.append("callbackUrl", callbackUrl);
|
|
343
|
+
if (language !== void 0) form.append("language", language);
|
|
344
|
+
if (useDraftVersion) form.append("useDraftVersion", "true");
|
|
345
|
+
return form;
|
|
346
|
+
},
|
|
347
|
+
signal
|
|
293
348
|
);
|
|
294
349
|
}
|
|
295
350
|
|
|
296
351
|
// src/sdk/api/run-partial-flow.ts
|
|
297
352
|
function buildRequest(flowId, parts, params, asyncMode) {
|
|
298
|
-
const form = new FormData();
|
|
299
|
-
for (const part of parts) {
|
|
300
|
-
form.append("files", new File([part.data], part.filename, { type: part.contentType }));
|
|
301
|
-
}
|
|
302
|
-
if (params.context !== void 0) form.append("jsonContext", JSON.stringify(params.context));
|
|
303
|
-
form.append("executionScope", params.executionScope);
|
|
304
|
-
if (params.flowVersion !== void 0) form.append("flowVersion", params.flowVersion);
|
|
305
|
-
for (const name of params.classificationNodeNames ?? []) {
|
|
306
|
-
form.append("classificationNodeNames", name);
|
|
307
|
-
}
|
|
308
353
|
const suffix = asyncMode ? "partial-execute-async" : "partial-execute";
|
|
309
354
|
const path = `/flows/${flowId}/${suffix}`;
|
|
310
355
|
return {
|
|
311
356
|
path: params.organization ? `${path}?organization=${encodeURIComponent(params.organization)}` : path,
|
|
312
|
-
|
|
357
|
+
createForm: () => {
|
|
358
|
+
const form = new FormData();
|
|
359
|
+
for (const part of parts) form.append("files", part.data, part.filename);
|
|
360
|
+
if (params.context !== void 0) form.append("jsonContext", JSON.stringify(params.context));
|
|
361
|
+
form.append("executionScope", params.executionScope);
|
|
362
|
+
if (params.flowVersion !== void 0) form.append("flowVersion", params.flowVersion);
|
|
363
|
+
for (const name of params.classificationNodeNames ?? []) {
|
|
364
|
+
form.append("classificationNodeNames", name);
|
|
365
|
+
}
|
|
366
|
+
if (asyncMode && "callbackUrl" in params && params.callbackUrl !== void 0) {
|
|
367
|
+
form.append("callbackUrl", params.callbackUrl);
|
|
368
|
+
}
|
|
369
|
+
if (asyncMode && "language" in params && params.language !== void 0) {
|
|
370
|
+
form.append("language", params.language);
|
|
371
|
+
}
|
|
372
|
+
return form;
|
|
373
|
+
}
|
|
313
374
|
};
|
|
314
375
|
}
|
|
315
376
|
async function runPartialFlow(http, flowId, parts, params, signal) {
|
|
316
|
-
const { path,
|
|
317
|
-
return http
|
|
377
|
+
const { path, createForm } = buildRequest(flowId, parts, params, false);
|
|
378
|
+
return requestMultipartJson(http, path, createForm, signal);
|
|
318
379
|
}
|
|
319
380
|
async function runPartialFlowAsync(http, flowId, parts, params, signal) {
|
|
320
|
-
const { path,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
381
|
+
const { path, createForm } = buildRequest(flowId, parts, params, true);
|
|
382
|
+
return requestMultipartJson(
|
|
383
|
+
http,
|
|
384
|
+
path,
|
|
385
|
+
createForm,
|
|
325
386
|
signal
|
|
326
|
-
|
|
387
|
+
);
|
|
327
388
|
}
|
|
328
389
|
|
|
329
390
|
// src/sdk/status.ts
|
|
@@ -381,15 +442,19 @@ async function getResult(http, executionResultId, signal, language, organization
|
|
|
381
442
|
async function startFraudDetection(http, params, signal) {
|
|
382
443
|
if (params.file !== void 0) {
|
|
383
444
|
const [part] = await resolveInputs({ files: [params.file] });
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
445
|
+
return requestMultipartJson(
|
|
446
|
+
http,
|
|
447
|
+
"/fraud-detections",
|
|
448
|
+
() => {
|
|
449
|
+
const form = new FormData();
|
|
450
|
+
form.append("file", part.data, part.filename);
|
|
451
|
+
if (params.analystContext !== void 0) {
|
|
452
|
+
form.append("analystContext", params.analystContext);
|
|
453
|
+
}
|
|
454
|
+
return form;
|
|
455
|
+
},
|
|
391
456
|
signal
|
|
392
|
-
|
|
457
|
+
);
|
|
393
458
|
}
|
|
394
459
|
return http.requestJson("POST", "/fraud-detections", {
|
|
395
460
|
headers: { "content-type": "application/json" },
|
|
@@ -992,7 +1057,7 @@ var HELP = {
|
|
|
992
1057
|
|
|
993
1058
|
// src/cli/config-store.ts
|
|
994
1059
|
import { existsSync } from "fs";
|
|
995
|
-
import { mkdir, readFile
|
|
1060
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
996
1061
|
import { dirname, resolve } from "path";
|
|
997
1062
|
import { createInterface } from "readline/promises";
|
|
998
1063
|
|
|
@@ -1023,7 +1088,7 @@ async function loadCliConfig(configPathOption, cwd, requireExisting) {
|
|
|
1023
1088
|
}
|
|
1024
1089
|
let raw;
|
|
1025
1090
|
try {
|
|
1026
|
-
raw = await
|
|
1091
|
+
raw = await readFile(configPath, "utf8");
|
|
1027
1092
|
} catch {
|
|
1028
1093
|
throw new CliUsageError(`Unable to read config file: ${configPath}`);
|
|
1029
1094
|
}
|
|
@@ -1057,7 +1122,7 @@ function createFileTokenCache(path) {
|
|
|
1057
1122
|
async read() {
|
|
1058
1123
|
if (!existsSync(path)) return null;
|
|
1059
1124
|
try {
|
|
1060
|
-
const parsed = JSON.parse(await
|
|
1125
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
1061
1126
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
1062
1127
|
const value = parsed;
|
|
1063
1128
|
if (typeof value.accessToken !== "string" || typeof value.expiresAt !== "number" || typeof value.clientId !== "string" || typeof value.tokenEndpoint !== "string" || typeof value.scope !== "string") {
|