@docmana/sdk 0.32.0 → 0.36.0-hitl.2
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 +49 -0
- package/README.md +170 -0
- package/dist/cli.mjs +243 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/errors.d.ts +5 -0
- package/dist/execution-events.d.ts +47 -0
- package/dist/http/http-client.d.ts +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +305 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +295 -2
- package/dist/index.mjs.map +1 -1
- package/dist/models/execution-status.enum.d.ts +1 -1
- package/dist/review-events.d.ts +36 -0
- package/dist/reviews.d.ts +180 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.36.0-hitl.2 - 2026-10-23 (HITL prerelease)
|
|
4
|
+
|
|
5
|
+
- Add `executionEvents.list()` and typed, non-consuming sequence-paged node
|
|
6
|
+
history for formulas with or without human review. Add the expanded
|
|
7
|
+
`verifyExecutionEventCallback` / `parseExecutionEventCallback` while retaining
|
|
8
|
+
the existing strict review-only helpers and final callback behavior.
|
|
9
|
+
- Publish pilot versions under the explicit `hitl` npm distribution tag;
|
|
10
|
+
other prereleases use `next` and stable releases use `latest`.
|
|
11
|
+
|
|
12
|
+
## 0.36.0-hitl.1 - 2026-10-23 (local POC)
|
|
13
|
+
|
|
14
|
+
- Add `reviews.calculation()` and exported `ReviewCalculation` for non-consuming
|
|
15
|
+
provisional calculation state, coherent/target revisions, review counts and
|
|
16
|
+
per-revision node reuse. Preserve legacy null metadata, organization scope,
|
|
17
|
+
versioned decisions, assignment and final callback behavior.
|
|
18
|
+
|
|
19
|
+
## 0.35.0-hitl.3 - 2026-10-22 (local POC)
|
|
20
|
+
|
|
21
|
+
- Add optional `ReviewTask.findings` and exported `ReviewFinding` for explicit,
|
|
22
|
+
review-scoped field signals. Preserve missing/empty findings and existing
|
|
23
|
+
corrections, assignment, global confirmation and final callbacks.
|
|
24
|
+
|
|
25
|
+
## 0.35.0-hitl.2 - 2026-10-22 (local POC)
|
|
26
|
+
|
|
27
|
+
- Add optional read-only frozen review assessments (node score, description, feedback, explanation and reasoning). No per-field findings or decision/callback change.
|
|
28
|
+
|
|
29
|
+
## 0.35.0-hitl.1 - 2026-10-22 (local POC)
|
|
30
|
+
|
|
31
|
+
- Preserve optional native review lifecycle timestamps for Live's human-wait and activity display. No final result fetch, pipeline or callback contract change.
|
|
32
|
+
|
|
33
|
+
## 0.34.0 - 2026-10-22
|
|
34
|
+
|
|
35
|
+
- Add `reviews.document(id, docKey, "preview", options)` and exported
|
|
36
|
+
`DocumentPreviewManifest` for progressive import-time WebP preparation.
|
|
37
|
+
Existing document streams, review decisions and final callbacks are unchanged.
|
|
38
|
+
|
|
39
|
+
## 0.33.0 - 2026-10-21
|
|
40
|
+
|
|
41
|
+
- Add typed review event callbacks and raw-body HMAC signature verification for
|
|
42
|
+
Consumer's separate review event relay. Existing execution callbacks are unchanged.
|
|
43
|
+
- Add `Docmana.reviews` for organization-scoped queues, task details, versioned
|
|
44
|
+
decisions and streaming page/content/location reads. Preserve range/cache
|
|
45
|
+
statuses and safe conflict metadata; never automatically retry decisions.
|
|
46
|
+
- Expose `AWAITING_REVIEW` as a non-terminal execution status.
|
|
47
|
+
|
|
3
48
|
## Unreleased
|
|
4
49
|
|
|
5
50
|
## 0.32.0 - 2026-10-19
|
|
@@ -257,3 +302,7 @@ consu## 0.16.0
|
|
|
257
302
|
- Typed error hierarchy (`DocmanaError`, `DocmanaExecutionError`, …).
|
|
258
303
|
- Flexible file inputs: path, `Buffer`, `Stream`, and multi-file uploads.
|
|
259
304
|
- Dual ESM + CJS distribution with bundled type definitions.
|
|
305
|
+
|
|
306
|
+
### Local HITL outline addition
|
|
307
|
+
|
|
308
|
+
- Add reviews.outline with typed published formula steps, edges and review marks. Existing execution, decision and final callback contracts are unchanged.
|
package/README.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @docmana/sdk
|
|
2
2
|
|
|
3
|
+
The HITL pilot uses the explicit prerelease `0.36.0-hitl.2` and the npm `hitl`
|
|
4
|
+
distribution tag. Install the exact version for reproducible pilot builds:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install --save-exact @docmana/sdk@0.36.0-hitl.2
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The stable default remains on `latest`; the prerelease does not select a native
|
|
11
|
+
API. Configure the SDK's Consumer base URL on the application server.
|
|
12
|
+
|
|
13
|
+
## Real formula node progress (local HITL pilot)
|
|
14
|
+
|
|
15
|
+
`sdk.executionEvents.list(executionId, { organization, after: 0, limit: 100, signal })`
|
|
16
|
+
reads a non-consuming metadata journal, including formulas without human review.
|
|
17
|
+
Follow `nextAfter` while non-null; order by `sequence`, deduplicate `eventId`, and
|
|
18
|
+
honor `truncated` when the cursor predates retained history. The native journal
|
|
19
|
+
keeps at most 2,000 events with the execution. No PDF or final-result retention
|
|
20
|
+
is added. Older APIs can return 404; show confirmed status without inventing
|
|
21
|
+
progress. Never turn a journal failure into a call to another backend.
|
|
22
|
+
|
|
23
|
+
The four new names are `execution.node.started`, `execution.node.completed`,
|
|
24
|
+
`execution.node.failed`, and `execution.node.reused`. Events identify a node,
|
|
25
|
+
document keys, revision and attempt. A reused output does not imply a new LLM
|
|
26
|
+
call. Completion of a node is technical completion, not a positive business
|
|
27
|
+
verdict or final execution completion. `occurredAt` is the real action time;
|
|
28
|
+
`publishedAt` is optional broker time, present on signed callbacks.
|
|
29
|
+
|
|
30
|
+
`verifyExecutionEventCallback` and `parseExecutionEventCallback` accept both the
|
|
31
|
+
new node metadata and the four existing review events. Existing review-only
|
|
32
|
+
verification helpers remain strict and unchanged. Final execution callbacks and
|
|
33
|
+
result ownership keep their existing contract; there are no pipeline events.
|
|
34
|
+
|
|
35
|
+
## Review event callbacks
|
|
36
|
+
|
|
37
|
+
SDK 0.33.0 adds server-side `verifyReviewEventCallback`, `parseReviewEventCallback`
|
|
38
|
+
and the `ReviewEventCallback` type for Consumer's opt-in review event relay.
|
|
39
|
+
This is independent from `runFlowAsync({ callbackUrl })`, which still receives only
|
|
40
|
+
the final result. No Service Bus connection is required in your application.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { verifyReviewEventCallback } from "@docmana/sdk";
|
|
44
|
+
const event = verifyReviewEventCallback({
|
|
45
|
+
body: rawUtf8Body,
|
|
46
|
+
signature: request.headers.get("x-docmana-event-signature"),
|
|
47
|
+
timestamp: request.headers.get("x-docmana-event-timestamp"),
|
|
48
|
+
secret: serverConfiguredSecret, // at least 32 characters; never sent to a browser
|
|
49
|
+
organization: expectedOrganization,
|
|
50
|
+
});
|
|
51
|
+
await persistOnce(event.organization, event.eventId, event);
|
|
52
|
+
// Only now return 2xx. Never finalize an execution from a review notification.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Supported types are `execution.awaiting_review`, `execution.review_done`,
|
|
56
|
+
`execution.review_closed` and `execution.review_expired`. `review_done` means the
|
|
57
|
+
decision is recorded, not that the execution is finished. `review_closed` also
|
|
58
|
+
describes the review rather than the execution. `publishedAt` is the broker's
|
|
59
|
+
publication time. The metadata excludes document names, values, notes and results.
|
|
60
|
+
Signatures use HMAC-SHA256 over `timestamp + "." + rawBody` with header `v1=<hex>`;
|
|
61
|
+
verification accepts a five-minute clock difference. Broker retries are re-signed
|
|
62
|
+
but retain the event ID; store it durably for deduplication. An event notification
|
|
63
|
+
never consumes the final result. Source publication is best-effort: reconcile
|
|
64
|
+
current review state separately after a missed notification.
|
|
65
|
+
|
|
3
66
|
Official Docmana SDK for calling `docmana-consumer-edge-api`.
|
|
4
67
|
|
|
5
68
|
SDK 0.32.0 adds native formula authoring, assisted conversations and revision-pinned
|
|
@@ -18,6 +81,17 @@ status, and returns the mapped Consumer execution DTO.
|
|
|
18
81
|
npm install @docmana/sdk
|
|
19
82
|
```
|
|
20
83
|
|
|
84
|
+
### GitHub release recovery
|
|
85
|
+
|
|
86
|
+
The Release workflow publishes an existing `v<version>` tag. GitHub Actions are
|
|
87
|
+
pinned to full commit SHAs to satisfy the enterprise policy. If a tag-triggered
|
|
88
|
+
run fails before starting, merge the workflow correction first, then manually
|
|
89
|
+
run **Release** from `main` with that existing tag. The workflow checks out the
|
|
90
|
+
tag and verifies its package version before publishing; do not move a release
|
|
91
|
+
tag to repair a pipeline. Publication requires the existing npm publisher
|
|
92
|
+
credential or trusted-publishing configuration. Verify the version is available
|
|
93
|
+
in npm before updating a consumer's lockfile.
|
|
94
|
+
|
|
21
95
|
Requires Node.js 20+.
|
|
22
96
|
|
|
23
97
|
## Quickstart
|
|
@@ -450,6 +524,85 @@ user confirmation before submitting the same file with `confirmOverwrite: true`.
|
|
|
450
524
|
`remove(id, options)` permanently deletes the formula and its authoring data;
|
|
451
525
|
confirm that action with the user. Import and deletion do not use operation IDs.
|
|
452
526
|
|
|
527
|
+
## Human review of formula executions
|
|
528
|
+
|
|
529
|
+
Read calculation metadata repeatedly with
|
|
530
|
+
`client.reviews.calculation(executionId, { organization })`. It returns
|
|
531
|
+
`executionResultId`, normalized `executionStatus` and `calculation` (null for
|
|
532
|
+
legacy/unmarked runs). Calculation includes `status`, coherent `revision`
|
|
533
|
+
(0 before the first calculation), optional `targetRevision`, `provisional`,
|
|
534
|
+
review counts and computed/reused node counts for that revision. Node counts
|
|
535
|
+
are not individual model-call counts. Calculation revision is distinct from
|
|
536
|
+
`ReviewTask.version`, which remains required for decisions. A completed
|
|
537
|
+
calculation stays provisional until the persisted execution is terminal.
|
|
538
|
+
This read is available before the first review and after final callback delivery;
|
|
539
|
+
it never consumes the single-use final result or triggers pipelines/callbacks.
|
|
540
|
+
|
|
541
|
+
Requires Consumer's review routes and the compatible HITL Document API branch.
|
|
542
|
+
No pipelines are required. Start published manual formulas with `runFlowAsync`;
|
|
543
|
+
`AWAITING_REVIEW` is non-terminal. `runFlow` still waits for final completion and
|
|
544
|
+
retains its configured timeout, so do not hold an HTTP request open for human work.
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
const scope = { organization: "your-organization", signal: controller.signal };
|
|
548
|
+
const { reviews, nextCursor } = await docmana.reviews.list({
|
|
549
|
+
...scope,
|
|
550
|
+
status: "pending",
|
|
551
|
+
limit: 50,
|
|
552
|
+
});
|
|
553
|
+
const task = await docmana.reviews.get(reviews[0].executionResultId, scope);
|
|
554
|
+
await docmana.reviews.decide(
|
|
555
|
+
task.executionResultId,
|
|
556
|
+
{
|
|
557
|
+
decision: "continue",
|
|
558
|
+
version: task.version,
|
|
559
|
+
reviewer: authenticatedServerActor, // Never trust an identity from the browser.
|
|
560
|
+
corrections: [{ itemId: task.documents[0].items![0].itemId, value: false }],
|
|
561
|
+
},
|
|
562
|
+
scope,
|
|
563
|
+
);
|
|
564
|
+
// Preparation starts at import. Poll queued/processing; other states are terminal.
|
|
565
|
+
const preparation = await docmana.reviews.document(
|
|
566
|
+
task.executionResultId,
|
|
567
|
+
task.documents[0].docKey,
|
|
568
|
+
"preview",
|
|
569
|
+
{
|
|
570
|
+
...scope,
|
|
571
|
+
version: task.reviewId,
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
const manifest = await preparation.json(); // DocumentPreviewManifest
|
|
575
|
+
// Only fetch numbers present in manifest.pages; page 1 precedes later batches.
|
|
576
|
+
// Original content remains available as the PDF fallback.
|
|
577
|
+
const page = await docmana.reviews.document(
|
|
578
|
+
task.executionResultId,
|
|
579
|
+
"doc01",
|
|
580
|
+
{ page: 1 },
|
|
581
|
+
{
|
|
582
|
+
...scope,
|
|
583
|
+
version: task.reviewId,
|
|
584
|
+
size: "thumb",
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
// page is a streaming Fetch Response. Consume or cancel its body; do not buffer a large PDF.
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
`list` accepts `pending`, `submitted`, `resuming` and an opaque `cursor`. It lists
|
|
591
|
+
open tasks only. The `"*"` dossier entry is not a file. Use the returned item IDs
|
|
592
|
+
unchanged; JSON values preserve `null`, `false`, `0`, objects and arrays.
|
|
593
|
+
`document` also accepts `"content"` or `"locations"`, Range/If-Range and ETag
|
|
594
|
+
options. It returns 200/206/304/416 without consuming the body.
|
|
595
|
+
|
|
596
|
+
A decision receipt means **recorded and resume queued**, never final completion.
|
|
597
|
+
On 409 inspect `DocmanaError.reviewConflict` and `apiCode`, then re-read the task.
|
|
598
|
+
On 503 re-read first: the decision may already be recorded. The SDK does not retry
|
|
599
|
+
a decision after a conflict, 503 or uncertain transport outcome. The standard one-time
|
|
600
|
+
401 token-refresh retry still applies. Keep the existing final callback/polling result owner;
|
|
601
|
+
opening a review must not consume the single-use execution result.
|
|
602
|
+
|
|
603
|
+
Release order: compatible Document API, Consumer, published SDK, then Arcana's
|
|
604
|
+
exact pinned dependency. This local 0.33.0 candidate is not published by these steps.
|
|
605
|
+
|
|
453
606
|
## License
|
|
454
607
|
|
|
455
608
|
MIT
|
|
@@ -476,3 +629,20 @@ Use `authoring.trialDocument(fileBlob, filename, spec, { organization, signal })
|
|
|
476
629
|
For assisted Mongo nodes, call `authoring.documentTrialDefinitions({ flowId, expectedRevisions, node | allNodes }, { organization, signal })`. This read-only preparation pins active node-owned rules and settings to the coherent native snapshot, checking revisions again after resolution. Pass the returned definition and uploaded file to the server-side `runAssistedDocumentTrial` library in `@docmana/docmana-flows`. Git callers use Flows' `prepareDocumentTrial` directly. Neither path requires `/extract/test-assisted` or a modified DocMana API. Calling the old native trial method with assisted nodes now fails before upload with HTTP 422.
|
|
477
630
|
|
|
478
631
|
Flows owns assisted IDP/model calls and requires its server-side provider configuration. It analyzes the file once and tries up to 100 compatible classification/extraction nodes independently, retaining evidence envelopes. Files are nonempty PDF/images up to 20 MiB. Definitions and uploads use disposable local storage, cleaned after completion; no formula, rule, reference expectation or publication is saved. These trials do not execute production routing, translate results, compare expectations or produce release approval evidence. Missing upstream context is rejected. Native API accounting and PromptShield are not implied by the Flows executor. No new CLI command is introduced.
|
|
632
|
+
|
|
633
|
+
### Formula steps during human review
|
|
634
|
+
|
|
635
|
+
Task detail also preserves optional native `createdAt`, `submittedAt`, `resumingAt` and `closedAt` timestamps. Human waiting is bounded by task creation and native decision acceptance; review closure does not prove final execution completion. Older backends can omit these fields; clients must show an unavailable duration rather than infer it from callback delivery time.
|
|
636
|
+
|
|
637
|
+
Use client.reviews.outline(flowId, { organization }) to read the published nodes, edges, ordering and human-review marks through Consumer. Compare outline.version with the review flowVersion before displaying progress. This read never consumes an execution result and does not introduce pipelines.
|
|
638
|
+
|
|
639
|
+
### Frozen review evidence (local HITL pilot)
|
|
640
|
+
|
|
641
|
+
`reviews.get()` optionally returns `documents[].assessments`: the stored node score (0–100), `scoreDescription`, `feedback`, `explanation` and `reasoning` from the same frozen snapshot as the items. These are read-only node-level evidence, not uncertain-field findings. Older backends may omit assessments or scores; do not infer missing scores. Decisions still send only editable item corrections through Consumer.
|
|
642
|
+
|
|
643
|
+
`ReviewTask.findings?: ReviewFinding[]` separately carries explicit field signals:
|
|
644
|
+
stable review-scoped `id`, `code`, `message`, resolved `itemIds`, and optional
|
|
645
|
+
`source` (`model` or `rule`). Consumers must not infer fields from node scores or
|
|
646
|
+
explanation text. Missing or empty findings preserve the existing presentation.
|
|
647
|
+
These are frozen evidence, not per-field decisions; editing a value does not
|
|
648
|
+
erase its finding. Continue using the same global, versioned decision call.
|
package/dist/cli.mjs
CHANGED
|
@@ -171,6 +171,234 @@ var FormulaAuthoring = class {
|
|
|
171
171
|
}
|
|
172
172
|
};
|
|
173
173
|
|
|
174
|
+
// src/sdk/reviews.ts
|
|
175
|
+
function segment(value) {
|
|
176
|
+
if (!value || value === "." || value === ".." || /[\\/]/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32))
|
|
177
|
+
throw new Error("Invalid review identifier");
|
|
178
|
+
return encodeURIComponent(value);
|
|
179
|
+
}
|
|
180
|
+
var Reviews = class {
|
|
181
|
+
constructor(http) {
|
|
182
|
+
this.http = http;
|
|
183
|
+
}
|
|
184
|
+
http;
|
|
185
|
+
/** Repeatable metadata read: never consumes the final execution result. */
|
|
186
|
+
calculation(executionId, options) {
|
|
187
|
+
return this.http.requestJson("GET", `/executions/${segment(executionId)}/review/calculation`, {
|
|
188
|
+
signal: options.signal,
|
|
189
|
+
query: { organization: options.organization }
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/** Published formula structure only. Compare its version with the review's flowVersion. */
|
|
193
|
+
outline(flowId, options) {
|
|
194
|
+
return this.http.requestJson("GET", `/flows/${segment(flowId)}/outline`, {
|
|
195
|
+
signal: options.signal,
|
|
196
|
+
query: { organization: options.organization }
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
list(options) {
|
|
200
|
+
return this.http.requestJson("GET", "/reviews", {
|
|
201
|
+
signal: options.signal,
|
|
202
|
+
query: {
|
|
203
|
+
organization: options.organization,
|
|
204
|
+
...options.status ? { status: options.status } : {},
|
|
205
|
+
...options.cursor ? { cursor: options.cursor } : {},
|
|
206
|
+
...options.limit ? { limit: String(options.limit) } : {}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
get(executionId, options) {
|
|
211
|
+
return this.http.requestJson("GET", `/executions/${segment(executionId)}/review`, {
|
|
212
|
+
signal: options.signal,
|
|
213
|
+
query: { organization: options.organization }
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
decide(executionId, decision, options) {
|
|
217
|
+
return this.http.requestJson("POST", `/executions/${segment(executionId)}/review`, {
|
|
218
|
+
signal: options.signal,
|
|
219
|
+
query: { organization: options.organization },
|
|
220
|
+
headers: { "content-type": "application/json" },
|
|
221
|
+
body: JSON.stringify(decision)
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/** Streaming Response: callers own consumption/cancellation. Never buffers the PDF. */
|
|
225
|
+
document(executionId, docKey, resource, options) {
|
|
226
|
+
if (typeof resource === "object" && (!Number.isSafeInteger(resource.page) || resource.page < 1 || resource.page > 999999))
|
|
227
|
+
throw new Error("Invalid page number");
|
|
228
|
+
const path = typeof resource === "object" ? `pages/${resource.page}` : resource;
|
|
229
|
+
const headers = Object.fromEntries(
|
|
230
|
+
Object.entries({
|
|
231
|
+
range: options.range,
|
|
232
|
+
"if-range": options.ifRange,
|
|
233
|
+
"if-none-match": options.ifNoneMatch,
|
|
234
|
+
"if-modified-since": options.ifModifiedSince
|
|
235
|
+
}).filter((entry) => typeof entry[1] === "string")
|
|
236
|
+
);
|
|
237
|
+
return this.http.requestRaw(
|
|
238
|
+
"GET",
|
|
239
|
+
`/executions/${segment(executionId)}/review/documents/${segment(docKey)}/${path}`,
|
|
240
|
+
{
|
|
241
|
+
signal: options.signal,
|
|
242
|
+
headers,
|
|
243
|
+
acceptStatuses: [304, 416],
|
|
244
|
+
query: {
|
|
245
|
+
organization: options.organization,
|
|
246
|
+
...options.version ? { v: options.version } : {},
|
|
247
|
+
...options.size ? { size: options.size } : {}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/sdk/execution-events.ts
|
|
255
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
256
|
+
|
|
257
|
+
// src/sdk/review-events.ts
|
|
258
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
259
|
+
var REVIEW_EVENT_TYPES = [
|
|
260
|
+
"execution.awaiting_review",
|
|
261
|
+
"execution.review_done",
|
|
262
|
+
"execution.review_closed",
|
|
263
|
+
"execution.review_expired"
|
|
264
|
+
];
|
|
265
|
+
var ReviewEventVerificationError = class extends Error {
|
|
266
|
+
constructor(reason) {
|
|
267
|
+
super(`Invalid review event ${reason}`);
|
|
268
|
+
this.reason = reason;
|
|
269
|
+
this.name = "ReviewEventVerificationError";
|
|
270
|
+
}
|
|
271
|
+
reason;
|
|
272
|
+
};
|
|
273
|
+
function parseReviewEventCallback(value) {
|
|
274
|
+
const fail = () => {
|
|
275
|
+
throw new ReviewEventVerificationError("payload");
|
|
276
|
+
};
|
|
277
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail();
|
|
278
|
+
const raw = value;
|
|
279
|
+
const text = (key) => {
|
|
280
|
+
const v = raw[key];
|
|
281
|
+
if (typeof v !== "string" || !v.trim() || v.length > 512 || [...v].some((char) => char.charCodeAt(0) < 32))
|
|
282
|
+
return fail();
|
|
283
|
+
return v;
|
|
284
|
+
};
|
|
285
|
+
if (raw.schemaVersion !== 1 || !REVIEW_EVENT_TYPES.includes(raw.type))
|
|
286
|
+
return fail();
|
|
287
|
+
const event = {
|
|
288
|
+
schemaVersion: 1,
|
|
289
|
+
eventId: text("eventId"),
|
|
290
|
+
type: raw.type,
|
|
291
|
+
organization: text("organization"),
|
|
292
|
+
executionResultId: text("executionResultId"),
|
|
293
|
+
reviewId: text("reviewId"),
|
|
294
|
+
publishedAt: text("publishedAt")
|
|
295
|
+
};
|
|
296
|
+
if (!Number.isFinite(Date.parse(event.publishedAt))) return fail();
|
|
297
|
+
for (const key of ["flowId", "nodeId"])
|
|
298
|
+
if (raw[key] !== void 0) event[key] = text(key);
|
|
299
|
+
for (const key of ["flowVersion", "reviewVersion"]) {
|
|
300
|
+
if (raw[key] === void 0) continue;
|
|
301
|
+
if (!Number.isSafeInteger(raw[key]) || Number(raw[key]) < 1) return fail();
|
|
302
|
+
event[key] = Number(raw[key]);
|
|
303
|
+
}
|
|
304
|
+
if (raw.documentKeys !== void 0) {
|
|
305
|
+
if (!Array.isArray(raw.documentKeys) || raw.documentKeys.length > 1e3 || raw.documentKeys.some((v) => typeof v !== "string" || !v || v.length > 512))
|
|
306
|
+
return fail();
|
|
307
|
+
event.documentKeys = raw.documentKeys;
|
|
308
|
+
}
|
|
309
|
+
if (raw.decision !== void 0) {
|
|
310
|
+
if (!["continue", "exclude", "rerun", "reject"].includes(String(raw.decision))) return fail();
|
|
311
|
+
event.decision = raw.decision;
|
|
312
|
+
}
|
|
313
|
+
if (raw.reviewStatus !== void 0) {
|
|
314
|
+
if (!["completed", "rejected", "failed"].includes(String(raw.reviewStatus))) return fail();
|
|
315
|
+
event.reviewStatus = raw.reviewStatus;
|
|
316
|
+
}
|
|
317
|
+
if (event.type === "execution.review_done" && (!event.decision || !event.reviewVersion))
|
|
318
|
+
return fail();
|
|
319
|
+
if (event.type === "execution.review_closed" && !event.reviewStatus) return fail();
|
|
320
|
+
return event;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/sdk/execution-events.ts
|
|
324
|
+
var EXECUTION_NODE_EVENT_TYPES = [
|
|
325
|
+
"execution.node.started",
|
|
326
|
+
"execution.node.completed",
|
|
327
|
+
"execution.node.failed",
|
|
328
|
+
"execution.node.reused"
|
|
329
|
+
];
|
|
330
|
+
function parseExecutionEventCallback(value) {
|
|
331
|
+
const raw = value;
|
|
332
|
+
if (!raw || !EXECUTION_NODE_EVENT_TYPES.includes(raw.type))
|
|
333
|
+
return parseReviewEventCallback(value);
|
|
334
|
+
const fail = () => {
|
|
335
|
+
throw new ReviewEventVerificationError("payload");
|
|
336
|
+
};
|
|
337
|
+
const text = (key) => {
|
|
338
|
+
const v = raw[key];
|
|
339
|
+
return typeof v === "string" && v.length > 0 && v.length <= 512 && [...v].every((c) => c.charCodeAt(0) >= 32) ? v : fail();
|
|
340
|
+
};
|
|
341
|
+
const integer = (key) => Number.isSafeInteger(raw[key]) && Number(raw[key]) >= 1 ? Number(raw[key]) : fail();
|
|
342
|
+
if (raw.schemaVersion !== 1) return fail();
|
|
343
|
+
const event = {
|
|
344
|
+
schemaVersion: 1,
|
|
345
|
+
eventId: text("eventId"),
|
|
346
|
+
type: raw.type,
|
|
347
|
+
organization: text("organization"),
|
|
348
|
+
executionResultId: text("executionResultId"),
|
|
349
|
+
sequence: integer("sequence"),
|
|
350
|
+
occurredAt: text("occurredAt"),
|
|
351
|
+
nodeId: text("nodeId"),
|
|
352
|
+
nodeType: text("nodeType"),
|
|
353
|
+
revision: integer("revision"),
|
|
354
|
+
attemptId: text("attemptId"),
|
|
355
|
+
documentKeys: []
|
|
356
|
+
};
|
|
357
|
+
if (!Number.isFinite(Date.parse(event.occurredAt))) return fail();
|
|
358
|
+
if (!Array.isArray(raw.documentKeys) || !raw.documentKeys.length || raw.documentKeys.length > 1e3 || raw.documentKeys.some(
|
|
359
|
+
(v) => typeof v !== "string" || !v || v.length > 512 || [...v].some((c) => c.charCodeAt(0) < 32)
|
|
360
|
+
))
|
|
361
|
+
return fail();
|
|
362
|
+
event.documentKeys = raw.documentKeys;
|
|
363
|
+
if (raw.flowId !== void 0) event.flowId = text("flowId");
|
|
364
|
+
if (raw.flowVersion !== void 0) event.flowVersion = integer("flowVersion");
|
|
365
|
+
if (raw.publishedAt !== void 0) {
|
|
366
|
+
event.publishedAt = text("publishedAt");
|
|
367
|
+
if (!Number.isFinite(Date.parse(event.publishedAt))) return fail();
|
|
368
|
+
}
|
|
369
|
+
return event;
|
|
370
|
+
}
|
|
371
|
+
var ExecutionEvents = class {
|
|
372
|
+
constructor(http) {
|
|
373
|
+
this.http = http;
|
|
374
|
+
}
|
|
375
|
+
http;
|
|
376
|
+
/** One bounded page; never polls for completion or consumes a final result. */
|
|
377
|
+
async list(id, options) {
|
|
378
|
+
const after = options.after ?? 0, limit = options.limit ?? 100;
|
|
379
|
+
if (!id || id.length > 512 || id === "." || id === ".." || /[\\/]/.test(id) || [...id].some((c) => c.charCodeAt(0) < 32) || !options.organization?.trim() || !Number.isSafeInteger(after) || after < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
380
|
+
throw new Error("Invalid execution event query");
|
|
381
|
+
const page = await this.http.requestJson(
|
|
382
|
+
"GET",
|
|
383
|
+
`/executions/${encodeURIComponent(id)}/events`,
|
|
384
|
+
{
|
|
385
|
+
signal: options.signal,
|
|
386
|
+
query: { organization: options.organization, after: String(after), limit: String(limit) }
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
if (page.executionResultId !== id || !Array.isArray(page.events) || page.events.length > limit || !Number.isSafeInteger(page.lastSequence) || page.lastSequence < 0 || typeof page.truncated !== "boolean")
|
|
390
|
+
throw new ReviewEventVerificationError("payload");
|
|
391
|
+
const events = page.events.map((value) => parseExecutionEventCallback(value));
|
|
392
|
+
if (events.some(
|
|
393
|
+
(event, i) => !("sequence" in event) || event.executionResultId !== id || event.organization !== options.organization || event.sequence <= (i ? events[i - 1].sequence : after) || event.sequence > page.lastSequence
|
|
394
|
+
))
|
|
395
|
+
throw new ReviewEventVerificationError("payload");
|
|
396
|
+
if (page.nextAfter !== null && (!Number.isSafeInteger(page.nextAfter) || page.nextAfter <= after || page.nextAfter !== events.at(-1)?.sequence))
|
|
397
|
+
throw new ReviewEventVerificationError("payload");
|
|
398
|
+
return { ...page, events };
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
174
402
|
// src/sdk/errors.ts
|
|
175
403
|
var DocmanaError = class extends Error {
|
|
176
404
|
status;
|
|
@@ -178,6 +406,7 @@ var DocmanaError = class extends Error {
|
|
|
178
406
|
code;
|
|
179
407
|
/** Public API business code; `code` retains the SDK's existing error category. */
|
|
180
408
|
apiCode;
|
|
409
|
+
reviewConflict;
|
|
181
410
|
constructor(message, opts = {}) {
|
|
182
411
|
super(message);
|
|
183
412
|
this.name = new.target.name;
|
|
@@ -303,7 +532,7 @@ var HttpClient = class {
|
|
|
303
532
|
if (res.status === 401 && this.opts.getAccessToken) {
|
|
304
533
|
res = await this.send(method, path, options, true);
|
|
305
534
|
}
|
|
306
|
-
if (!res.ok) {
|
|
535
|
+
if (!res.ok && !options.acceptStatuses?.includes(res.status)) {
|
|
307
536
|
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
308
537
|
const message = await this.extractMessage(res);
|
|
309
538
|
const error = errorFromResponse(
|
|
@@ -315,6 +544,15 @@ var HttpClient = class {
|
|
|
315
544
|
const data = await res.clone().json().catch(() => null);
|
|
316
545
|
if (typeof data?.code === "string" && /^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(data.code))
|
|
317
546
|
Object.defineProperty(error, "apiCode", { value: data.code, enumerable: true });
|
|
547
|
+
if (res.status === 409 && data?.details)
|
|
548
|
+
Object.defineProperty(error, "reviewConflict", {
|
|
549
|
+
value: {
|
|
550
|
+
version: Number.isInteger(data.details.version) ? data.details.version : null,
|
|
551
|
+
submittedBy: typeof data.details.submittedBy === "string" ? data.details.submittedBy.slice(0, 200) : null,
|
|
552
|
+
submittedAt: typeof data.details.submittedAt === "string" ? data.details.submittedAt.slice(0, 40) : null
|
|
553
|
+
},
|
|
554
|
+
enumerable: true
|
|
555
|
+
});
|
|
318
556
|
throw error;
|
|
319
557
|
}
|
|
320
558
|
return res;
|
|
@@ -816,6 +1054,8 @@ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
|
|
|
816
1054
|
// src/sdk/client.ts
|
|
817
1055
|
var Docmana = class {
|
|
818
1056
|
authoring;
|
|
1057
|
+
reviews;
|
|
1058
|
+
executionEvents;
|
|
819
1059
|
config;
|
|
820
1060
|
http;
|
|
821
1061
|
constructor(config) {
|
|
@@ -828,6 +1068,8 @@ var Docmana = class {
|
|
|
828
1068
|
headers: this.config.headers
|
|
829
1069
|
});
|
|
830
1070
|
this.authoring = new FormulaAuthoring(this.http);
|
|
1071
|
+
this.reviews = new Reviews(this.http);
|
|
1072
|
+
this.executionEvents = new ExecutionEvents(this.http);
|
|
831
1073
|
}
|
|
832
1074
|
async runFlowAsync(flowId, params, options = {}) {
|
|
833
1075
|
const parts = await resolveInputs(params);
|