@docmana/sdk 0.32.0 → 0.36.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 +60 -0
- package/LICENSE +21 -21
- package/README.md +183 -0
- package/dist/cli.mjs +265 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/errors.d.ts +17 -0
- package/dist/execution-events.d.ts +47 -0
- package/dist/http/http-client.d.ts +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +327 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +317 -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 +188 -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,11 @@ 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 && isRecord(data?.details))
|
|
582
|
+
Object.defineProperty(error, "reviewConflict", {
|
|
583
|
+
value: reviewConflictDetails(data.details),
|
|
584
|
+
enumerable: true
|
|
585
|
+
});
|
|
310
586
|
throw error;
|
|
311
587
|
}
|
|
312
588
|
return res;
|
|
@@ -350,6 +626,32 @@ var HttpClient = class {
|
|
|
350
626
|
}
|
|
351
627
|
}
|
|
352
628
|
};
|
|
629
|
+
function isRecord(value) {
|
|
630
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
631
|
+
}
|
|
632
|
+
function reviewConflictDetails(raw) {
|
|
633
|
+
const text = (value, limit) => typeof value === "string" ? value.slice(0, limit) : null;
|
|
634
|
+
const version = (value) => Number.isSafeInteger(value) && Number(value) >= 1 ? value : null;
|
|
635
|
+
const actor = isRecord(raw.decidedBy) ? raw.decidedBy : {};
|
|
636
|
+
const id = text(actor.id, 200);
|
|
637
|
+
const name = text(actor.name, 200);
|
|
638
|
+
const source = actor.source === "integrator" || actor.source === "token" ? actor.source : null;
|
|
639
|
+
const decidedBy = id !== null || name !== null || source !== null ? {
|
|
640
|
+
...id !== null ? { id } : {},
|
|
641
|
+
...name !== null ? { name } : {},
|
|
642
|
+
...source ? { source } : {}
|
|
643
|
+
} : null;
|
|
644
|
+
const currentVersion = version(raw.currentVersion) ?? version(raw.version);
|
|
645
|
+
const decidedAt = text(raw.decidedAt, 40) ?? text(raw.submittedAt, 40);
|
|
646
|
+
return {
|
|
647
|
+
currentVersion,
|
|
648
|
+
decidedBy,
|
|
649
|
+
decidedAt,
|
|
650
|
+
version: currentVersion,
|
|
651
|
+
submittedBy: id ?? name ?? text(raw.submittedBy, 200),
|
|
652
|
+
submittedAt: decidedAt
|
|
653
|
+
};
|
|
654
|
+
}
|
|
353
655
|
function parseRetryAfterMs(value) {
|
|
354
656
|
if (!value) return void 0;
|
|
355
657
|
const seconds = Number(value);
|
|
@@ -808,6 +1110,8 @@ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
|
|
|
808
1110
|
// src/sdk/client.ts
|
|
809
1111
|
var Docmana = class {
|
|
810
1112
|
authoring;
|
|
1113
|
+
reviews;
|
|
1114
|
+
executionEvents;
|
|
811
1115
|
config;
|
|
812
1116
|
http;
|
|
813
1117
|
constructor(config) {
|
|
@@ -820,6 +1124,8 @@ var Docmana = class {
|
|
|
820
1124
|
headers: this.config.headers
|
|
821
1125
|
});
|
|
822
1126
|
this.authoring = new FormulaAuthoring(this.http);
|
|
1127
|
+
this.reviews = new Reviews(this.http);
|
|
1128
|
+
this.executionEvents = new ExecutionEvents(this.http);
|
|
823
1129
|
}
|
|
824
1130
|
async runFlowAsync(flowId, params, options = {}) {
|
|
825
1131
|
const parts = await resolveInputs(params);
|
|
@@ -1005,6 +1311,15 @@ export {
|
|
|
1005
1311
|
DocmanaRequestError,
|
|
1006
1312
|
DocmanaServiceUnavailableError,
|
|
1007
1313
|
DocmanaTimeoutError,
|
|
1008
|
-
|
|
1314
|
+
EXECUTION_NODE_EVENT_TYPES,
|
|
1315
|
+
ExecutionEvents,
|
|
1316
|
+
FormulaAuthoring,
|
|
1317
|
+
REVIEW_EVENT_TYPES,
|
|
1318
|
+
ReviewEventVerificationError,
|
|
1319
|
+
Reviews,
|
|
1320
|
+
parseExecutionEventCallback,
|
|
1321
|
+
parseReviewEventCallback,
|
|
1322
|
+
verifyExecutionEventCallback,
|
|
1323
|
+
verifyReviewEventCallback
|
|
1009
1324
|
};
|
|
1010
1325
|
//# sourceMappingURL=index.mjs.map
|