@docmana/sdk 0.28.1 → 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/cli.mjs CHANGED
@@ -36,11 +36,377 @@ function normalizeApiBaseUrl(value) {
36
36
  return /\/v\d+$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
37
37
  }
38
38
 
39
+ // src/sdk/authoring.ts
40
+ function identity(id) {
41
+ if (!/^[A-Za-z0-9_-]{1,200}$/.test(id)) throw new Error("Invalid formula identifier");
42
+ return encodeURIComponent(id);
43
+ }
44
+ var FormulaAuthoring = class {
45
+ constructor(http) {
46
+ this.http = http;
47
+ }
48
+ http;
49
+ /** Read-only, revision-pinned definitions. Assisted execution belongs to the server-side Flows POC. */
50
+ documentTrialDefinitions(spec, options) {
51
+ identity(spec.flowId);
52
+ if (Boolean(spec.node) === (spec.allNodes === true))
53
+ throw new Error("Select one node or all nodes");
54
+ if (spec.node) identity(spec.node);
55
+ return this.request("POST", "document-trial/definitions", options, spec);
56
+ }
57
+ /** Native manual execution through Consumer Edge; assisted trials use definitions plus server-side Flows. */
58
+ async trialDocument(file, filename, spec, options) {
59
+ if (!options.organization.trim()) throw new Error("Organization is required");
60
+ if (!file.size || file.size > 20 * 1024 * 1024 || !/\.(pdf|jpeg|jpg|png|bmp|tiff|tif|heic)$/i.test(filename))
61
+ throw new Error("Invalid trial document");
62
+ if ("flowId" in spec) {
63
+ identity(spec.flowId);
64
+ if (Boolean(spec.node) === (spec.allNodes === true))
65
+ throw new Error("Select one node or all nodes");
66
+ if (spec.node) identity(spec.node);
67
+ } else if (!Array.isArray(spec.nodes) || !spec.nodes.length || spec.nodes.length > 100)
68
+ throw new Error("Invalid trial nodes");
69
+ const body = new FormData();
70
+ body.set("file", file, filename);
71
+ body.set("spec", JSON.stringify(spec));
72
+ return this.http.requestJson("POST", "/authoring/document-trial", {
73
+ query: { organization: options.organization },
74
+ signal: options.signal,
75
+ body
76
+ });
77
+ }
78
+ editorCatalog(options) {
79
+ return this.request("GET", "editor-catalog", options);
80
+ }
81
+ generateMapping(body, options) {
82
+ return this.request("POST", "generate-mapping", options, body);
83
+ }
84
+ openConversation(body, options) {
85
+ identity(body.flow_id);
86
+ if (body.node_id) identity(body.node_id);
87
+ return this.request("POST", "conversations", options, body);
88
+ }
89
+ sendConversationMessage(id, content, options) {
90
+ return this.request("POST", `conversations/${identity(id)}/messages`, options, { content });
91
+ }
92
+ confirmConversation(id, body, options) {
93
+ return this.request("POST", `conversations/${identity(id)}/confirm`, options, body);
94
+ }
95
+ request(method, path, options, body) {
96
+ if (!options.organization.trim()) throw new Error("Organization is required");
97
+ return this.http.requestJson(method, `/authoring/${path}`, {
98
+ query: { organization: options.organization },
99
+ signal: options.signal,
100
+ ...body === void 0 ? {} : {
101
+ body: JSON.stringify(body),
102
+ headers: { "content-type": "application/json" }
103
+ }
104
+ });
105
+ }
106
+ list(options) {
107
+ const offset = options.offset ?? 0, limit = options.limit ?? 50;
108
+ if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(limit) || limit < 1 || limit > 200)
109
+ throw new Error("Invalid authoring pagination");
110
+ return this.request(
111
+ "GET",
112
+ `flows?offset=${offset}&limit=${limit}&sort=${encodeURIComponent(options.sort ?? "id")}`,
113
+ options
114
+ );
115
+ }
116
+ remove(id, options) {
117
+ return this.request("DELETE", `flows/${identity(id)}`, options);
118
+ }
119
+ async exportArchive(id, options) {
120
+ if (!options.organization.trim()) throw new Error("Organization is required");
121
+ return (await this.http.requestBytes("GET", `/authoring/flows/${identity(id)}/export`, {
122
+ query: { organization: options.organization },
123
+ signal: options.signal
124
+ })).bytes;
125
+ }
126
+ importArchive(file, filename, options) {
127
+ if (!options.organization.trim()) throw new Error("Organization is required");
128
+ if (!/\.(zip|docmana)$/i.test(filename) || file.size > 10 * 1024 * 1024)
129
+ throw new Error("Invalid formula archive");
130
+ const body = new FormData();
131
+ body.append("file", file, filename);
132
+ return this.http.requestJson("POST", "/authoring/flows/import", {
133
+ query: {
134
+ organization: options.organization,
135
+ confirmOverwrite: String(options.confirmOverwrite === true)
136
+ },
137
+ body,
138
+ signal: options.signal
139
+ });
140
+ }
141
+ get(id, options) {
142
+ return this.request("GET", `flows/${identity(id)}`, options);
143
+ }
144
+ create(body, options) {
145
+ return this.request("POST", "flows", options, body);
146
+ }
147
+ save(id, body, options) {
148
+ return this.request("PATCH", `flows/${identity(id)}/draft`, options, body);
149
+ }
150
+ validate(id, revisions, options) {
151
+ return this.request("POST", `flows/${identity(id)}/validate`, options, {
152
+ expectedRevisions: revisions
153
+ });
154
+ }
155
+ publish(id, body, options) {
156
+ return this.request("POST", `flows/${identity(id)}/publish`, options, body);
157
+ }
158
+ history(id, kind, options) {
159
+ if (kind !== "snapshots" && kind !== "published") throw new Error("Invalid history kind");
160
+ const offset = options.offset ?? 0, limit = options.limit ?? 20;
161
+ if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(limit) || limit < 1 || limit > 100)
162
+ throw new Error("Invalid history pagination");
163
+ return this.request(
164
+ "GET",
165
+ `flows/${identity(id)}/history/${kind}?offset=${offset}&limit=${limit}`,
166
+ options
167
+ );
168
+ }
169
+ flags(options) {
170
+ return this.request("GET", "feature-flags", options);
171
+ }
172
+ };
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
+
39
402
  // src/sdk/errors.ts
40
403
  var DocmanaError = class extends Error {
41
404
  status;
42
405
  requestId;
43
406
  code;
407
+ /** Public API business code; `code` retains the SDK's existing error category. */
408
+ apiCode;
409
+ reviewConflict;
44
410
  constructor(message, opts = {}) {
45
411
  super(message);
46
412
  this.name = new.target.name;
@@ -166,15 +532,28 @@ var HttpClient = class {
166
532
  if (res.status === 401 && this.opts.getAccessToken) {
167
533
  res = await this.send(method, path, options, true);
168
534
  }
169
- if (!res.ok) {
535
+ if (!res.ok && !options.acceptStatuses?.includes(res.status)) {
170
536
  const requestId = res.headers.get("x-request-id") ?? void 0;
171
537
  const message = await this.extractMessage(res);
172
- throw errorFromResponse(
538
+ const error = errorFromResponse(
173
539
  res.status,
174
540
  message,
175
541
  requestId,
176
542
  parseRetryAfterMs(res.headers.get("retry-after"))
177
543
  );
544
+ const data = await res.clone().json().catch(() => null);
545
+ if (typeof data?.code === "string" && /^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(data.code))
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
+ });
556
+ throw error;
178
557
  }
179
558
  return res;
180
559
  }
@@ -674,6 +1053,9 @@ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
674
1053
 
675
1054
  // src/sdk/client.ts
676
1055
  var Docmana = class {
1056
+ authoring;
1057
+ reviews;
1058
+ executionEvents;
677
1059
  config;
678
1060
  http;
679
1061
  constructor(config) {
@@ -685,6 +1067,9 @@ var Docmana = class {
685
1067
  getAccessToken: this.config.getAccessToken,
686
1068
  headers: this.config.headers
687
1069
  });
1070
+ this.authoring = new FormulaAuthoring(this.http);
1071
+ this.reviews = new Reviews(this.http);
1072
+ this.executionEvents = new ExecutionEvents(this.http);
688
1073
  }
689
1074
  async runFlowAsync(flowId, params, options = {}) {
690
1075
  const parts = await resolveInputs(params);