@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/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
- FormulaAuthoring
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