@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/dist/index.mjs
CHANGED
|
@@ -163,6 +163,276 @@ var FormulaAuthoring = class {
|
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
+
// src/sdk/reviews.ts
|
|
167
|
+
function segment(value) {
|
|
168
|
+
if (!value || value === "." || value === ".." || /[\\/]/.test(value) || [...value].some((char) => char.charCodeAt(0) < 32))
|
|
169
|
+
throw new Error("Invalid review identifier");
|
|
170
|
+
return encodeURIComponent(value);
|
|
171
|
+
}
|
|
172
|
+
var Reviews = class {
|
|
173
|
+
constructor(http) {
|
|
174
|
+
this.http = http;
|
|
175
|
+
}
|
|
176
|
+
http;
|
|
177
|
+
/** Repeatable metadata read: never consumes the final execution result. */
|
|
178
|
+
calculation(executionId, options) {
|
|
179
|
+
return this.http.requestJson("GET", `/executions/${segment(executionId)}/review/calculation`, {
|
|
180
|
+
signal: options.signal,
|
|
181
|
+
query: { organization: options.organization }
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/** Published formula structure only. Compare its version with the review's flowVersion. */
|
|
185
|
+
outline(flowId, options) {
|
|
186
|
+
return this.http.requestJson("GET", `/flows/${segment(flowId)}/outline`, {
|
|
187
|
+
signal: options.signal,
|
|
188
|
+
query: { organization: options.organization }
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
list(options) {
|
|
192
|
+
return this.http.requestJson("GET", "/reviews", {
|
|
193
|
+
signal: options.signal,
|
|
194
|
+
query: {
|
|
195
|
+
organization: options.organization,
|
|
196
|
+
...options.status ? { status: options.status } : {},
|
|
197
|
+
...options.cursor ? { cursor: options.cursor } : {},
|
|
198
|
+
...options.limit ? { limit: String(options.limit) } : {}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
get(executionId, options) {
|
|
203
|
+
return this.http.requestJson("GET", `/executions/${segment(executionId)}/review`, {
|
|
204
|
+
signal: options.signal,
|
|
205
|
+
query: { organization: options.organization }
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
decide(executionId, decision, options) {
|
|
209
|
+
return this.http.requestJson("POST", `/executions/${segment(executionId)}/review`, {
|
|
210
|
+
signal: options.signal,
|
|
211
|
+
query: { organization: options.organization },
|
|
212
|
+
headers: { "content-type": "application/json" },
|
|
213
|
+
body: JSON.stringify(decision)
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Streaming Response: callers own consumption/cancellation. Never buffers the PDF. */
|
|
217
|
+
document(executionId, docKey, resource, options) {
|
|
218
|
+
if (typeof resource === "object" && (!Number.isSafeInteger(resource.page) || resource.page < 1 || resource.page > 999999))
|
|
219
|
+
throw new Error("Invalid page number");
|
|
220
|
+
const path = typeof resource === "object" ? `pages/${resource.page}` : resource;
|
|
221
|
+
const headers = Object.fromEntries(
|
|
222
|
+
Object.entries({
|
|
223
|
+
range: options.range,
|
|
224
|
+
"if-range": options.ifRange,
|
|
225
|
+
"if-none-match": options.ifNoneMatch,
|
|
226
|
+
"if-modified-since": options.ifModifiedSince
|
|
227
|
+
}).filter((entry) => typeof entry[1] === "string")
|
|
228
|
+
);
|
|
229
|
+
return this.http.requestRaw(
|
|
230
|
+
"GET",
|
|
231
|
+
`/executions/${segment(executionId)}/review/documents/${segment(docKey)}/${path}`,
|
|
232
|
+
{
|
|
233
|
+
signal: options.signal,
|
|
234
|
+
headers,
|
|
235
|
+
acceptStatuses: [304, 416],
|
|
236
|
+
query: {
|
|
237
|
+
organization: options.organization,
|
|
238
|
+
...options.version ? { v: options.version } : {},
|
|
239
|
+
...options.size ? { size: options.size } : {}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/sdk/execution-events.ts
|
|
247
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
248
|
+
|
|
249
|
+
// src/sdk/review-events.ts
|
|
250
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
251
|
+
var REVIEW_EVENT_TYPES = [
|
|
252
|
+
"execution.awaiting_review",
|
|
253
|
+
"execution.review_done",
|
|
254
|
+
"execution.review_closed",
|
|
255
|
+
"execution.review_expired"
|
|
256
|
+
];
|
|
257
|
+
var ReviewEventVerificationError = class extends Error {
|
|
258
|
+
constructor(reason) {
|
|
259
|
+
super(`Invalid review event ${reason}`);
|
|
260
|
+
this.reason = reason;
|
|
261
|
+
this.name = "ReviewEventVerificationError";
|
|
262
|
+
}
|
|
263
|
+
reason;
|
|
264
|
+
};
|
|
265
|
+
function parseReviewEventCallback(value) {
|
|
266
|
+
const fail = () => {
|
|
267
|
+
throw new ReviewEventVerificationError("payload");
|
|
268
|
+
};
|
|
269
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail();
|
|
270
|
+
const raw = value;
|
|
271
|
+
const text = (key) => {
|
|
272
|
+
const v = raw[key];
|
|
273
|
+
if (typeof v !== "string" || !v.trim() || v.length > 512 || [...v].some((char) => char.charCodeAt(0) < 32))
|
|
274
|
+
return fail();
|
|
275
|
+
return v;
|
|
276
|
+
};
|
|
277
|
+
if (raw.schemaVersion !== 1 || !REVIEW_EVENT_TYPES.includes(raw.type))
|
|
278
|
+
return fail();
|
|
279
|
+
const event = {
|
|
280
|
+
schemaVersion: 1,
|
|
281
|
+
eventId: text("eventId"),
|
|
282
|
+
type: raw.type,
|
|
283
|
+
organization: text("organization"),
|
|
284
|
+
executionResultId: text("executionResultId"),
|
|
285
|
+
reviewId: text("reviewId"),
|
|
286
|
+
publishedAt: text("publishedAt")
|
|
287
|
+
};
|
|
288
|
+
if (!Number.isFinite(Date.parse(event.publishedAt))) return fail();
|
|
289
|
+
for (const key of ["flowId", "nodeId"])
|
|
290
|
+
if (raw[key] !== void 0) event[key] = text(key);
|
|
291
|
+
for (const key of ["flowVersion", "reviewVersion"]) {
|
|
292
|
+
if (raw[key] === void 0) continue;
|
|
293
|
+
if (!Number.isSafeInteger(raw[key]) || Number(raw[key]) < 1) return fail();
|
|
294
|
+
event[key] = Number(raw[key]);
|
|
295
|
+
}
|
|
296
|
+
if (raw.documentKeys !== void 0) {
|
|
297
|
+
if (!Array.isArray(raw.documentKeys) || raw.documentKeys.length > 1e3 || raw.documentKeys.some((v) => typeof v !== "string" || !v || v.length > 512))
|
|
298
|
+
return fail();
|
|
299
|
+
event.documentKeys = raw.documentKeys;
|
|
300
|
+
}
|
|
301
|
+
if (raw.decision !== void 0) {
|
|
302
|
+
if (!["continue", "exclude", "rerun", "reject"].includes(String(raw.decision))) return fail();
|
|
303
|
+
event.decision = raw.decision;
|
|
304
|
+
}
|
|
305
|
+
if (raw.reviewStatus !== void 0) {
|
|
306
|
+
if (!["completed", "rejected", "failed"].includes(String(raw.reviewStatus))) return fail();
|
|
307
|
+
event.reviewStatus = raw.reviewStatus;
|
|
308
|
+
}
|
|
309
|
+
if (event.type === "execution.review_done" && (!event.decision || !event.reviewVersion))
|
|
310
|
+
return fail();
|
|
311
|
+
if (event.type === "execution.review_closed" && !event.reviewStatus) return fail();
|
|
312
|
+
return event;
|
|
313
|
+
}
|
|
314
|
+
function verifyReviewEventCallback(options) {
|
|
315
|
+
const { body, signature, timestamp, secret } = options;
|
|
316
|
+
if (!timestamp || !/^\d{10,13}$/.test(timestamp))
|
|
317
|
+
throw new ReviewEventVerificationError("timestamp");
|
|
318
|
+
const age = Math.abs((options.nowMs ?? Date.now()) / 1e3 - Number(timestamp));
|
|
319
|
+
if (age > (options.toleranceSeconds ?? 300)) throw new ReviewEventVerificationError("timestamp");
|
|
320
|
+
if (secret.length < 32 || !signature || !/^v1=[a-f0-9]{64}$/.test(signature))
|
|
321
|
+
throw new ReviewEventVerificationError("signature");
|
|
322
|
+
const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest();
|
|
323
|
+
if (!timingSafeEqual(expected, Buffer.from(signature.slice(3), "hex")))
|
|
324
|
+
throw new ReviewEventVerificationError("signature");
|
|
325
|
+
let value;
|
|
326
|
+
try {
|
|
327
|
+
value = JSON.parse(body);
|
|
328
|
+
} catch {
|
|
329
|
+
throw new ReviewEventVerificationError("payload");
|
|
330
|
+
}
|
|
331
|
+
const event = parseReviewEventCallback(value);
|
|
332
|
+
if (options.organization !== void 0 && event.organization !== options.organization)
|
|
333
|
+
throw new ReviewEventVerificationError("organization");
|
|
334
|
+
return event;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/sdk/execution-events.ts
|
|
338
|
+
var EXECUTION_NODE_EVENT_TYPES = [
|
|
339
|
+
"execution.node.started",
|
|
340
|
+
"execution.node.completed",
|
|
341
|
+
"execution.node.failed",
|
|
342
|
+
"execution.node.reused"
|
|
343
|
+
];
|
|
344
|
+
function parseExecutionEventCallback(value) {
|
|
345
|
+
const raw = value;
|
|
346
|
+
if (!raw || !EXECUTION_NODE_EVENT_TYPES.includes(raw.type))
|
|
347
|
+
return parseReviewEventCallback(value);
|
|
348
|
+
const fail = () => {
|
|
349
|
+
throw new ReviewEventVerificationError("payload");
|
|
350
|
+
};
|
|
351
|
+
const text = (key) => {
|
|
352
|
+
const v = raw[key];
|
|
353
|
+
return typeof v === "string" && v.length > 0 && v.length <= 512 && [...v].every((c) => c.charCodeAt(0) >= 32) ? v : fail();
|
|
354
|
+
};
|
|
355
|
+
const integer = (key) => Number.isSafeInteger(raw[key]) && Number(raw[key]) >= 1 ? Number(raw[key]) : fail();
|
|
356
|
+
if (raw.schemaVersion !== 1) return fail();
|
|
357
|
+
const event = {
|
|
358
|
+
schemaVersion: 1,
|
|
359
|
+
eventId: text("eventId"),
|
|
360
|
+
type: raw.type,
|
|
361
|
+
organization: text("organization"),
|
|
362
|
+
executionResultId: text("executionResultId"),
|
|
363
|
+
sequence: integer("sequence"),
|
|
364
|
+
occurredAt: text("occurredAt"),
|
|
365
|
+
nodeId: text("nodeId"),
|
|
366
|
+
nodeType: text("nodeType"),
|
|
367
|
+
revision: integer("revision"),
|
|
368
|
+
attemptId: text("attemptId"),
|
|
369
|
+
documentKeys: []
|
|
370
|
+
};
|
|
371
|
+
if (!Number.isFinite(Date.parse(event.occurredAt))) return fail();
|
|
372
|
+
if (!Array.isArray(raw.documentKeys) || !raw.documentKeys.length || raw.documentKeys.length > 1e3 || raw.documentKeys.some(
|
|
373
|
+
(v) => typeof v !== "string" || !v || v.length > 512 || [...v].some((c) => c.charCodeAt(0) < 32)
|
|
374
|
+
))
|
|
375
|
+
return fail();
|
|
376
|
+
event.documentKeys = raw.documentKeys;
|
|
377
|
+
if (raw.flowId !== void 0) event.flowId = text("flowId");
|
|
378
|
+
if (raw.flowVersion !== void 0) event.flowVersion = integer("flowVersion");
|
|
379
|
+
if (raw.publishedAt !== void 0) {
|
|
380
|
+
event.publishedAt = text("publishedAt");
|
|
381
|
+
if (!Number.isFinite(Date.parse(event.publishedAt))) return fail();
|
|
382
|
+
}
|
|
383
|
+
return event;
|
|
384
|
+
}
|
|
385
|
+
function verifyExecutionEventCallback(options) {
|
|
386
|
+
const { body, signature, timestamp, secret } = options;
|
|
387
|
+
if (!timestamp || !/^\d{10,13}$/.test(timestamp) || Math.abs((options.nowMs ?? Date.now()) / 1e3 - Number(timestamp)) > (options.toleranceSeconds ?? 300))
|
|
388
|
+
throw new ReviewEventVerificationError("timestamp");
|
|
389
|
+
if (secret.length < 32 || !signature || !/^v1=[a-f0-9]{64}$/.test(signature))
|
|
390
|
+
throw new ReviewEventVerificationError("signature");
|
|
391
|
+
const expected = createHmac2("sha256", secret).update(`${timestamp}.${body}`).digest();
|
|
392
|
+
if (!timingSafeEqual2(expected, Buffer.from(signature.slice(3), "hex")))
|
|
393
|
+
throw new ReviewEventVerificationError("signature");
|
|
394
|
+
let value;
|
|
395
|
+
try {
|
|
396
|
+
value = JSON.parse(body);
|
|
397
|
+
} catch {
|
|
398
|
+
throw new ReviewEventVerificationError("payload");
|
|
399
|
+
}
|
|
400
|
+
const event = parseExecutionEventCallback(value);
|
|
401
|
+
if (options.organization !== void 0 && event.organization !== options.organization)
|
|
402
|
+
throw new ReviewEventVerificationError("organization");
|
|
403
|
+
return event;
|
|
404
|
+
}
|
|
405
|
+
var ExecutionEvents = class {
|
|
406
|
+
constructor(http) {
|
|
407
|
+
this.http = http;
|
|
408
|
+
}
|
|
409
|
+
http;
|
|
410
|
+
/** One bounded page; never polls for completion or consumes a final result. */
|
|
411
|
+
async list(id, options) {
|
|
412
|
+
const after = options.after ?? 0, limit = options.limit ?? 100;
|
|
413
|
+
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)
|
|
414
|
+
throw new Error("Invalid execution event query");
|
|
415
|
+
const page = await this.http.requestJson(
|
|
416
|
+
"GET",
|
|
417
|
+
`/executions/${encodeURIComponent(id)}/events`,
|
|
418
|
+
{
|
|
419
|
+
signal: options.signal,
|
|
420
|
+
query: { organization: options.organization, after: String(after), limit: String(limit) }
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
if (page.executionResultId !== id || !Array.isArray(page.events) || page.events.length > limit || !Number.isSafeInteger(page.lastSequence) || page.lastSequence < 0 || typeof page.truncated !== "boolean")
|
|
424
|
+
throw new ReviewEventVerificationError("payload");
|
|
425
|
+
const events = page.events.map((value) => parseExecutionEventCallback(value));
|
|
426
|
+
if (events.some(
|
|
427
|
+
(event, i) => !("sequence" in event) || event.executionResultId !== id || event.organization !== options.organization || event.sequence <= (i ? events[i - 1].sequence : after) || event.sequence > page.lastSequence
|
|
428
|
+
))
|
|
429
|
+
throw new ReviewEventVerificationError("payload");
|
|
430
|
+
if (page.nextAfter !== null && (!Number.isSafeInteger(page.nextAfter) || page.nextAfter <= after || page.nextAfter !== events.at(-1)?.sequence))
|
|
431
|
+
throw new ReviewEventVerificationError("payload");
|
|
432
|
+
return { ...page, events };
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
166
436
|
// src/sdk/errors.ts
|
|
167
437
|
var DocmanaError = class extends Error {
|
|
168
438
|
status;
|
|
@@ -170,6 +440,7 @@ var DocmanaError = class extends Error {
|
|
|
170
440
|
code;
|
|
171
441
|
/** Public API business code; `code` retains the SDK's existing error category. */
|
|
172
442
|
apiCode;
|
|
443
|
+
reviewConflict;
|
|
173
444
|
constructor(message, opts = {}) {
|
|
174
445
|
super(message);
|
|
175
446
|
this.name = new.target.name;
|
|
@@ -295,7 +566,7 @@ var HttpClient = class {
|
|
|
295
566
|
if (res.status === 401 && this.opts.getAccessToken) {
|
|
296
567
|
res = await this.send(method, path, options, true);
|
|
297
568
|
}
|
|
298
|
-
if (!res.ok) {
|
|
569
|
+
if (!res.ok && !options.acceptStatuses?.includes(res.status)) {
|
|
299
570
|
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
300
571
|
const message = await this.extractMessage(res);
|
|
301
572
|
const error = errorFromResponse(
|
|
@@ -307,6 +578,15 @@ var HttpClient = class {
|
|
|
307
578
|
const data = await res.clone().json().catch(() => null);
|
|
308
579
|
if (typeof data?.code === "string" && /^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(data.code))
|
|
309
580
|
Object.defineProperty(error, "apiCode", { value: data.code, enumerable: true });
|
|
581
|
+
if (res.status === 409 && data?.details)
|
|
582
|
+
Object.defineProperty(error, "reviewConflict", {
|
|
583
|
+
value: {
|
|
584
|
+
version: Number.isInteger(data.details.version) ? data.details.version : null,
|
|
585
|
+
submittedBy: typeof data.details.submittedBy === "string" ? data.details.submittedBy.slice(0, 200) : null,
|
|
586
|
+
submittedAt: typeof data.details.submittedAt === "string" ? data.details.submittedAt.slice(0, 40) : null
|
|
587
|
+
},
|
|
588
|
+
enumerable: true
|
|
589
|
+
});
|
|
310
590
|
throw error;
|
|
311
591
|
}
|
|
312
592
|
return res;
|
|
@@ -808,6 +1088,8 @@ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
|
|
|
808
1088
|
// src/sdk/client.ts
|
|
809
1089
|
var Docmana = class {
|
|
810
1090
|
authoring;
|
|
1091
|
+
reviews;
|
|
1092
|
+
executionEvents;
|
|
811
1093
|
config;
|
|
812
1094
|
http;
|
|
813
1095
|
constructor(config) {
|
|
@@ -820,6 +1102,8 @@ var Docmana = class {
|
|
|
820
1102
|
headers: this.config.headers
|
|
821
1103
|
});
|
|
822
1104
|
this.authoring = new FormulaAuthoring(this.http);
|
|
1105
|
+
this.reviews = new Reviews(this.http);
|
|
1106
|
+
this.executionEvents = new ExecutionEvents(this.http);
|
|
823
1107
|
}
|
|
824
1108
|
async runFlowAsync(flowId, params, options = {}) {
|
|
825
1109
|
const parts = await resolveInputs(params);
|
|
@@ -1005,6 +1289,15 @@ export {
|
|
|
1005
1289
|
DocmanaRequestError,
|
|
1006
1290
|
DocmanaServiceUnavailableError,
|
|
1007
1291
|
DocmanaTimeoutError,
|
|
1008
|
-
|
|
1292
|
+
EXECUTION_NODE_EVENT_TYPES,
|
|
1293
|
+
ExecutionEvents,
|
|
1294
|
+
FormulaAuthoring,
|
|
1295
|
+
REVIEW_EVENT_TYPES,
|
|
1296
|
+
ReviewEventVerificationError,
|
|
1297
|
+
Reviews,
|
|
1298
|
+
parseExecutionEventCallback,
|
|
1299
|
+
parseReviewEventCallback,
|
|
1300
|
+
verifyExecutionEventCallback,
|
|
1301
|
+
verifyReviewEventCallback
|
|
1009
1302
|
};
|
|
1010
1303
|
//# sourceMappingURL=index.mjs.map
|