@docmana/sdk 0.28.0 → 0.32.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/README.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Official Docmana SDK for calling `docmana-consumer-edge-api`.
4
4
 
5
+ SDK 0.32.0 adds native formula authoring, assisted conversations and revision-pinned
6
+ document-trial preparation. Deploy Consumer authoring support before publishing
7
+ and adopting this version. DocMana API main supports the ordinary Mongo path;
8
+ assisted document trials run in DocMana Flows. A local development tarball is not
9
+ an npm release. Optional Git/projection types remain POC contracts, not proof of
10
+ support by API main.
11
+
5
12
  The SDK sends documents to Consumer, starts an async flow, polls Consumer for
6
13
  status, and returns the mapped Consumer execution DTO.
7
14
 
@@ -345,6 +352,9 @@ from the Bearer token.
345
352
  Every SDK error extends `DocmanaError`, which carries `.status`, `.code`, and an
346
353
  optional `.requestId` when available.
347
354
 
355
+ The optional `.apiCode` preserves the public API business code (for example,
356
+ `RULE_INTEGRITY_VIOLATION`). `.code` keeps its existing SDK error category.
357
+
348
358
  | Error | Thrown when |
349
359
  | -------------------------------- | ----------------------------------------------------------------------------------------------- |
350
360
  | `DocmanaRequestError` | `400` bad request. |
@@ -379,6 +389,90 @@ For a one-shot publish to the local yalc store:
379
389
  npm run yalc:publish
380
390
  ```
381
391
 
392
+ ## Native formula authoring
393
+
394
+ SDK 0.30.0 adds the existing DocMana stateful assistant through
395
+ `authoring.openConversation({ flow_id, language, scope?, node_id?, messages? }, options)`,
396
+ `sendConversationMessage(conversationId, content, options)` and
397
+ `confirmConversation(conversationId, { rule_id?, document_type_rename? }, options)`.
398
+ All three require organization scope and accept an abort signal. Opening does
399
+ not call the LLM. Replies contain native context, suggestions and proposals;
400
+ the client must explicitly apply and save approved rules. Confirmation only
401
+ acknowledges acceptance; it does not save or publish. HTTP 410 means the session
402
+ expired: reopen with the prior transcript and resolved context, then send the
403
+ new message. Do not replay an uncertain network failure automatically.
404
+
405
+ `docmana.authoring` manages native formulas through Consumer Edge's
406
+ `/v1/authoring` endpoints. It requires the DOC-2385 Consumer deployment and the
407
+ DocMana API authoring endpoints. These methods do not manage Git or YAML.
408
+
409
+ ```ts
410
+ const options = { organization: "my-organization" };
411
+ const formulas = await docmana.authoring.list({ ...options, limit: 50, offset: 0 });
412
+ const snapshot = await docmana.authoring.get(formulas[0].id, options);
413
+ const command = {
414
+ operationId: crypto.randomUUID(),
415
+ expectedRevisions: snapshot.revisions,
416
+ flowPatch: { name: "Updated formula" },
417
+ };
418
+ const receipt = await docmana.authoring.save(snapshot.flow.id, command, options);
419
+ await docmana.authoring.validate(snapshot.flow.id, receipt.revisions, options);
420
+ // Publication is an explicit separate operation.
421
+ await docmana.authoring.publish(
422
+ snapshot.flow.id,
423
+ {
424
+ operationId: crypto.randomUUID(),
425
+ expectedRevisions: receipt.revisions,
426
+ },
427
+ options,
428
+ );
429
+ ```
430
+
431
+ Also available: `create`, `history(id, "snapshots" | "published", options)` and
432
+ `flags(options)`. Every call requires an organization and accepts an AbortSignal.
433
+ Lists return arrays; list limits are 1–200 and history limits are 1–100. List sorting is applied before pagination using `sort`: `id` (default), `name`, `newest`, `oldest`, `recentlyModified`, or `leastRecentlyModified`. Summaries include creation/modification dates and execution/document totals when available.
434
+ `get` returns the editable graph, assisted rules, revisions and history counts.
435
+ Create/save/publish return a receipt, not a reloaded graph. Reload with `get`
436
+ after a successful mutation when server-generated prompt/rule identities matter.
437
+
438
+ Retain the same operation ID **and identical body** when retrying an uncertain
439
+ mutation outcome. A 409 has `error.status === 409`: keep the user's draft and
440
+ resolve the conflict explicitly. The SDK never silently rebases or retries a
441
+ conflict. Native metadata remains opaque and must be preserved by editors;
442
+ server-owned identities, history and revisions are not patchable. Assisted
443
+ editing requires the organization's `assisted-flows` flag and backend roles.
444
+
445
+ Archive transfers use the existing DocMana `.docmana`/ZIP format, including
446
+ assisted rules. `exportArchive(id, options)` returns `Uint8Array` bytes.
447
+ `importArchive(blob, filename, options)` accepts one archive up to 10 MiB and
448
+ returns `imported` or `confirmation_required`. On a duplicate, obtain explicit
449
+ user confirmation before submitting the same file with `confirmOverwrite: true`.
450
+ `remove(id, options)` permanently deletes the formula and its authoring data;
451
+ confirm that action with the user. Import and deletion do not use operation IDs.
452
+
382
453
  ## License
383
454
 
384
455
  MIT
456
+
457
+ `authoring.editorCatalog(options)` retrieves organization-scoped models, tools, external-system field definitions, default prompts and feature flags for native node editors. External-system credentials are excluded. `authoring.generateMapping({ workflow_fields, expected_fields, current_mapping, user_instructions }, options)` returns `{ jsonataExpression }` from the native mapper; it does not save or publish the proposal.
458
+
459
+ ### Git publication provenance (local authoring POC)
460
+
461
+ The types preserve optional `authoringSource`, Git commit/artifact hashes and
462
+ projection fields for the POC contract. DocMana API main rejects these fields on
463
+ native create/save/publish, including an explicit `{kind: "mongo"}` discriminator;
464
+ omit them for ordinary Mongo authoring. The current Git POC uses the explicitly
465
+ local Mongo release adapter in DocMana Flows instead.
466
+
467
+ Optional response provenance remains lossless when a compatible backend provides
468
+ it. Consumer and the SDK do not synchronize repositories, verify a checkout,
469
+ compute a publication baseline or automatically publish newer commits. The
470
+ presence of a TypeScript field does not enable an unsupported backend capability.
471
+
472
+ ### Document trials
473
+
474
+ Use `authoring.trialDocument(fileBlob, filename, spec, { organization, signal })` for manual document-node trials. `spec` selects a saved native `flowId`, `expectedRevisions`, and either `node` or `allNodes`; alternatively it contains compiled YAML `nodes` with native prompts. Consumer calls the existing DocMana API test-fields/test-metadata endpoints. Temporary YAML prompts are removed in finally; abrupt process failure can leave one for administrator cleanup.
475
+
476
+ 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
+
478
+ 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.
@@ -0,0 +1,317 @@
1
+ import type { HttpClient } from "./http/http-client.js";
2
+ /** Native editor data is lossless: unknown metadata is retained during edits. */
3
+ export type AuthoringData = Record<string, unknown>;
4
+ export interface AuthoringNode extends AuthoringData {
5
+ id: string;
6
+ type: string;
7
+ position: {
8
+ x: number;
9
+ y: number;
10
+ };
11
+ data: AuthoringData & {
12
+ label: string;
13
+ type: string;
14
+ enabled?: boolean;
15
+ };
16
+ parentId?: string;
17
+ }
18
+ export interface AuthoringEdge extends AuthoringData {
19
+ id: string;
20
+ source: string;
21
+ target: string;
22
+ }
23
+ export interface AuthoringRevisions {
24
+ flow: number;
25
+ nodes: Record<string, number>;
26
+ edges: Record<string, number>;
27
+ rules: Record<string, number>;
28
+ }
29
+ export type AuthoringSource = {
30
+ kind: "mongo";
31
+ } | {
32
+ kind: "git";
33
+ formulaId: string;
34
+ repository: string;
35
+ path: string;
36
+ ref: string;
37
+ };
38
+ export interface AuthoringGitPublication {
39
+ source: Extract<AuthoringSource, {
40
+ kind: "git";
41
+ }>;
42
+ commitSha: string;
43
+ publishedVersion: number;
44
+ mongoRevisions: AuthoringRevisions;
45
+ contentHash: string;
46
+ publishedAt: string;
47
+ artifactHash?: string | null;
48
+ toolingHash?: string | null;
49
+ }
50
+ export interface AuthoringGitProjection {
51
+ source: Extract<AuthoringSource, {
52
+ kind: "git";
53
+ }>;
54
+ commitSha: string;
55
+ artifactHash: string;
56
+ toolingHash: string;
57
+ operationId: string;
58
+ mongoRevisions: AuthoringRevisions;
59
+ contentHash: string;
60
+ projectedAt: string;
61
+ }
62
+ export interface AuthoringPublication {
63
+ operationId: string;
64
+ expectedRevisions: AuthoringRevisions;
65
+ gitCommitSha?: string;
66
+ gitArtifactHash?: string;
67
+ gitToolingHash?: string;
68
+ }
69
+ export interface AuthoringFlowSummary extends AuthoringData {
70
+ authoringSource?: AuthoringSource | null;
71
+ lastGitPublication?: AuthoringGitPublication | null;
72
+ lastGitProjection?: AuthoringGitProjection | null;
73
+ createdAt?: string;
74
+ updatedAt?: string;
75
+ executionStats?: {
76
+ totalExecutions: number;
77
+ totalDocuments: number;
78
+ };
79
+ id: string;
80
+ name: string;
81
+ type?: "manual" | "assisted";
82
+ state?: string;
83
+ isDirty?: boolean;
84
+ }
85
+ export interface AuthoringSnapshot {
86
+ gitPublicationState?: {
87
+ status: "not_published" | "unchanged" | "diverged" | "staged";
88
+ currentContentHash: string;
89
+ } | null;
90
+ flow: AuthoringFlowSummary & {
91
+ configs?: AuthoringData;
92
+ nodes: AuthoringNode[];
93
+ edges: AuthoringEdge[];
94
+ };
95
+ rules: AuthoringData[];
96
+ revisions: AuthoringRevisions;
97
+ historyCounts: {
98
+ snapshots: number;
99
+ published: number;
100
+ };
101
+ }
102
+ export interface AuthoringChanges {
103
+ create?: AuthoringData[];
104
+ update?: {
105
+ id: string;
106
+ patch: AuthoringData;
107
+ }[];
108
+ delete?: string[];
109
+ }
110
+ export interface AuthoringDraft {
111
+ gitProjection?: {
112
+ commitSha: string;
113
+ artifactHash: string;
114
+ toolingHash: string;
115
+ };
116
+ authoringSource?: AuthoringSource;
117
+ operationId: string;
118
+ expectedRevisions: AuthoringRevisions;
119
+ flowPatch?: AuthoringData;
120
+ nodes?: AuthoringChanges;
121
+ edges?: AuthoringChanges;
122
+ prompts?: {
123
+ create?: AuthoringData[];
124
+ updateForNode?: string[];
125
+ delete?: string[];
126
+ upsertForNode?: {
127
+ id: string;
128
+ prompt: AuthoringData;
129
+ }[];
130
+ };
131
+ rules?: AuthoringData[];
132
+ }
133
+ export interface AuthoringCreate {
134
+ authoringSource?: AuthoringSource;
135
+ operationId: string;
136
+ id: string;
137
+ name: string;
138
+ type: "manual" | "assisted";
139
+ configs?: AuthoringData;
140
+ nodes: AuthoringNode[];
141
+ edges: AuthoringEdge[];
142
+ }
143
+ export interface AuthoringReceipt {
144
+ success: boolean;
145
+ id?: string;
146
+ version?: number;
147
+ flowUpdatedAt: string;
148
+ revisions: AuthoringRevisions;
149
+ committedRuleIds?: Record<string, string>;
150
+ }
151
+ export interface AuthoringOptions {
152
+ organization: string;
153
+ signal?: AbortSignal;
154
+ }
155
+ export interface AuthoringDocumentTrialNode {
156
+ id: string;
157
+ name: string;
158
+ type: "classification" | "extraction" | "metadata-extraction";
159
+ prompt?: {
160
+ metadata: AuthoringData;
161
+ messages: AuthoringData[];
162
+ schemaDatas: AuthoringData;
163
+ };
164
+ /** Native assisted definition; mutually exclusive with a manual prompt. */
165
+ assisted?: {
166
+ metadata: AuthoringData;
167
+ rules: AuthoringData[];
168
+ configs: AuthoringData;
169
+ };
170
+ }
171
+ export type AuthoringDocumentTrialSpec = {
172
+ flowId: string;
173
+ expectedRevisions: AuthoringRevisions;
174
+ node?: string;
175
+ allNodes?: boolean;
176
+ } | {
177
+ nodes: AuthoringDocumentTrialNode[];
178
+ };
179
+ export interface AuthoringDocumentTrialReport {
180
+ executor?: "flows" | "docmana-api";
181
+ mode: "document-trial";
182
+ execution_scope: "isolated";
183
+ status: "completed" | "error";
184
+ rows: {
185
+ node: string;
186
+ name: string;
187
+ type: string;
188
+ status: "completed" | "error";
189
+ seconds: number;
190
+ output?: AuthoringData;
191
+ error?: string;
192
+ }[];
193
+ }
194
+ export interface AuthoringEditorCatalog {
195
+ capabilities?: {
196
+ canonicalProjection?: number;
197
+ runtimeEnvironment?: string;
198
+ };
199
+ models: {
200
+ default_model: string;
201
+ assisted_model?: string;
202
+ ai_provider: string;
203
+ models: {
204
+ model_name: string;
205
+ reasoning_effort_values?: string[] | null;
206
+ default_reasoning_effort?: string | null;
207
+ }[];
208
+ };
209
+ tools: (AuthoringData & {
210
+ tool_id: string;
211
+ tool_name?: string;
212
+ type?: string;
213
+ short_description_fr?: string;
214
+ short_description_en?: string;
215
+ })[];
216
+ systems: {
217
+ id: string;
218
+ name: string;
219
+ expectedFields?: AuthoringData[];
220
+ }[];
221
+ defaults: {
222
+ aggregator: string;
223
+ };
224
+ flags: {
225
+ flags: Record<string, boolean>;
226
+ };
227
+ }
228
+ export interface AuthoringConversationContext {
229
+ flow_id: string;
230
+ scope?: string | null;
231
+ node_id?: string | null;
232
+ node_label?: string | null;
233
+ }
234
+ export interface AuthoringConversation {
235
+ conversation_id: string;
236
+ expires_at: string;
237
+ context: AuthoringConversationContext;
238
+ }
239
+ export interface AuthoringConversationInput {
240
+ flow_id: string;
241
+ language: "fr" | "en";
242
+ scope?: string;
243
+ node_id?: string;
244
+ messages?: {
245
+ role: "user" | "assistant";
246
+ content: string;
247
+ }[];
248
+ }
249
+ export interface AuthoringConversationTurn {
250
+ assistant_message: string;
251
+ options?: unknown[] | null;
252
+ proposed_rule?: AuthoringData | null;
253
+ proposed_document_type_rename?: AuthoringData | null;
254
+ context: AuthoringConversationContext;
255
+ target_resolved: boolean;
256
+ expires_at: string;
257
+ }
258
+ export type AuthoringSort = "id" | "newest" | "oldest" | "recentlyModified" | "leastRecentlyModified" | "name";
259
+ export interface AuthoringPageOptions extends AuthoringOptions {
260
+ sort?: AuthoringSort;
261
+ offset?: number;
262
+ limit?: number;
263
+ }
264
+ /** Aggregate authoring only. Caller retains operation IDs when retrying mutations. */
265
+ export declare class FormulaAuthoring {
266
+ private readonly http;
267
+ constructor(http: HttpClient);
268
+ /** Read-only, revision-pinned definitions. Assisted execution belongs to the server-side Flows POC. */
269
+ documentTrialDefinitions(spec: {
270
+ flowId: string;
271
+ expectedRevisions: AuthoringRevisions;
272
+ node?: string;
273
+ allNodes?: boolean;
274
+ }, options: AuthoringOptions): Promise<{
275
+ nodes: AuthoringDocumentTrialNode[];
276
+ }>;
277
+ /** Native manual execution through Consumer Edge; assisted trials use definitions plus server-side Flows. */
278
+ trialDocument(file: Blob, filename: string, spec: AuthoringDocumentTrialSpec, options: AuthoringOptions): Promise<AuthoringDocumentTrialReport>;
279
+ editorCatalog(options: AuthoringOptions): Promise<AuthoringEditorCatalog>;
280
+ generateMapping(body: {
281
+ workflow_fields: AuthoringData[];
282
+ expected_fields: AuthoringData[];
283
+ current_mapping: string;
284
+ user_instructions: string;
285
+ }, options: AuthoringOptions): Promise<{
286
+ jsonataExpression: string;
287
+ }>;
288
+ openConversation(body: AuthoringConversationInput, options: AuthoringOptions): Promise<AuthoringConversation>;
289
+ sendConversationMessage(id: string, content: string, options: AuthoringOptions): Promise<AuthoringConversationTurn>;
290
+ confirmConversation(id: string, body: {
291
+ rule_id?: string;
292
+ document_type_rename?: boolean;
293
+ }, options: AuthoringOptions): Promise<{
294
+ expires_at: string;
295
+ }>;
296
+ private request;
297
+ list(options: AuthoringPageOptions): Promise<AuthoringFlowSummary[]>;
298
+ remove(id: string, options: AuthoringOptions): Promise<{
299
+ success: boolean;
300
+ }>;
301
+ exportArchive(id: string, options: AuthoringOptions): Promise<Uint8Array>;
302
+ importArchive(file: Blob, filename: string, options: AuthoringOptions & {
303
+ confirmOverwrite?: boolean;
304
+ }): Promise<{
305
+ status: "imported" | "confirmation_required";
306
+ flowName?: string;
307
+ }>;
308
+ get(id: string, options: AuthoringOptions): Promise<AuthoringSnapshot>;
309
+ create(body: AuthoringCreate, options: AuthoringOptions): Promise<AuthoringReceipt>;
310
+ save(id: string, body: AuthoringDraft, options: AuthoringOptions): Promise<AuthoringReceipt>;
311
+ validate(id: string, revisions: AuthoringRevisions, options: AuthoringOptions): Promise<AuthoringData>;
312
+ publish(id: string, body: AuthoringPublication, options: AuthoringOptions): Promise<AuthoringReceipt>;
313
+ history(id: string, kind: "snapshots" | "published", options: AuthoringPageOptions): Promise<AuthoringData[]>;
314
+ flags(options: AuthoringOptions): Promise<{
315
+ flags: Record<string, boolean>;
316
+ }>;
317
+ }
package/dist/cli.mjs CHANGED
@@ -36,11 +36,148 @@ 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
+
39
174
  // src/sdk/errors.ts
40
175
  var DocmanaError = class extends Error {
41
176
  status;
42
177
  requestId;
43
178
  code;
179
+ /** Public API business code; `code` retains the SDK's existing error category. */
180
+ apiCode;
44
181
  constructor(message, opts = {}) {
45
182
  super(message);
46
183
  this.name = new.target.name;
@@ -169,12 +306,16 @@ var HttpClient = class {
169
306
  if (!res.ok) {
170
307
  const requestId = res.headers.get("x-request-id") ?? void 0;
171
308
  const message = await this.extractMessage(res);
172
- throw errorFromResponse(
309
+ const error = errorFromResponse(
173
310
  res.status,
174
311
  message,
175
312
  requestId,
176
313
  parseRetryAfterMs(res.headers.get("retry-after"))
177
314
  );
315
+ const data = await res.clone().json().catch(() => null);
316
+ if (typeof data?.code === "string" && /^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(data.code))
317
+ Object.defineProperty(error, "apiCode", { value: data.code, enumerable: true });
318
+ throw error;
178
319
  }
179
320
  return res;
180
321
  }
@@ -674,6 +815,7 @@ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
674
815
 
675
816
  // src/sdk/client.ts
676
817
  var Docmana = class {
818
+ authoring;
677
819
  config;
678
820
  http;
679
821
  constructor(config) {
@@ -685,6 +827,7 @@ var Docmana = class {
685
827
  getAccessToken: this.config.getAccessToken,
686
828
  headers: this.config.headers
687
829
  });
830
+ this.authoring = new FormulaAuthoring(this.http);
688
831
  }
689
832
  async runFlowAsync(flowId, params, options = {}) {
690
833
  const parts = await resolveInputs(params);