flexorch-sdk 0.2.3 → 0.3.1

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Dataset
3
- } from "./chunk-35RGZSFP.js";
3
+ } from "./chunk-4KCMMRD7.js";
4
4
 
5
5
  // src/errors.ts
6
6
  var FlexOrchError = class extends Error {
@@ -104,6 +104,12 @@ async function parseError(res) {
104
104
  function sleep(ms) {
105
105
  return new Promise((r) => setTimeout(r, ms));
106
106
  }
107
+ function unwrap(body) {
108
+ if (body !== null && typeof body === "object" && "data" in body && "error" in body && typeof body["status"] === "string") {
109
+ return body["data"];
110
+ }
111
+ return body;
112
+ }
107
113
  var Transport = class {
108
114
  baseUrl;
109
115
  defaultHeaders;
@@ -117,7 +123,7 @@ var Transport = class {
117
123
  this.fetchFn = fetchFn ?? globalThis.fetch.bind(globalThis);
118
124
  this.defaultHeaders = {
119
125
  "X-API-KEY": apiKey,
120
- "User-Agent": "flexorch-sdk-js/0.1.0"
126
+ "User-Agent": "flexorch-sdk-js/0.3.1"
121
127
  };
122
128
  }
123
129
  url(path, params) {
@@ -158,7 +164,7 @@ var Transport = class {
158
164
  if (!ct.includes("application/json")) return null;
159
165
  const text = await res.text();
160
166
  if (!text.trim()) return null;
161
- return JSON.parse(text);
167
+ return unwrap(JSON.parse(text));
162
168
  } catch (err) {
163
169
  clearTimeout(timer);
164
170
  if (err instanceof FlexOrchError) throw err;
@@ -191,13 +197,27 @@ var Transport = class {
191
197
  };
192
198
 
193
199
  // src/models/job.ts
200
+ function jobFeedbackFromDict(data) {
201
+ return {
202
+ id: String(data["id"] ?? ""),
203
+ jobId: String(data["job_id"] ?? ""),
204
+ rating: String(data["rating"] ?? ""),
205
+ issue: data["issue"] ?? null,
206
+ notes: data["notes"] ?? null,
207
+ createdAt: String(data["created_at"] ?? "")
208
+ };
209
+ }
194
210
  var Job = class _Job {
195
211
  id;
196
212
  status;
197
213
  qualityGrade;
198
214
  qualityScore;
199
215
  documentId;
216
+ /** Needed by buildDataset() — POST /datasets/build-from-execution/{executionId}. */
217
+ executionId;
200
218
  hasDataset;
219
+ /** Set from `dataset_summary.dataset_id` on a completed dataset_build job's response. */
220
+ datasetId;
201
221
  /**
202
222
  * True when the underlying pipeline execution completed but one or more
203
223
  * non-critical steps failed (e.g. structured extraction couldn't find a
@@ -216,21 +236,37 @@ var Job = class _Job {
216
236
  this.qualityGrade = data.qualityGrade;
217
237
  this.qualityScore = data.qualityScore;
218
238
  this.documentId = data.documentId;
239
+ this.executionId = data.executionId;
219
240
  this.hasDataset = data.hasDataset;
241
+ this.datasetId = data.datasetId;
220
242
  this.degraded = data.degraded;
221
243
  this.failureReason = data.failureReason;
222
244
  this._transport = data._transport;
223
245
  }
224
246
  static fromDict(data, transport) {
225
- const quality = data["quality"] ?? {};
226
247
  const executionSummary = data["execution_summary"];
248
+ const processingSummary = data["processing_summary"];
249
+ const datasetSummary = data["dataset_summary"];
250
+ let quality = data["quality"];
251
+ if (!quality && processingSummary) {
252
+ quality = processingSummary["quality"];
253
+ }
254
+ quality = quality ?? {};
255
+ const executionId = executionSummary?.["execution_id"] ?? processingSummary?.["execution_id"] ?? data["execution_id"] ?? null;
227
256
  return new _Job({
228
257
  id: String(data["job_id"] ?? data["id"] ?? ""),
229
258
  status: String(data["status"] ?? ""),
230
259
  qualityGrade: quality["grade"] ?? null,
231
260
  qualityScore: quality["score"] ?? null,
232
261
  documentId: data["document_id"] ?? null,
233
- hasDataset: Boolean(data["has_dataset"] ?? false),
262
+ executionId,
263
+ // dataset_summary is only present on a completed dataset_build job's
264
+ // response — neither has_dataset nor processing_summary is ever set
265
+ // for that job type, so without this .dataset() always returned null
266
+ // for the job.buildDataset().wait().dataset() chain even though the
267
+ // dataset had been built successfully.
268
+ hasDataset: Boolean(data["has_dataset"] ?? processingSummary?.["has_dataset"] ?? Boolean(datasetSummary) ?? false),
269
+ datasetId: datasetSummary?.["dataset_id"] ?? null,
234
270
  degraded: Boolean(executionSummary?.["degraded"] ?? false),
235
271
  failureReason: data["failure_reason"] ?? null,
236
272
  _transport: transport
@@ -258,15 +294,56 @@ var Job = class _Job {
258
294
  throw new JobTimeoutError(this.id, timeout);
259
295
  }
260
296
  async dataset() {
297
+ const { Dataset: Dataset2 } = await import("./dataset-E7EXJETI.js");
298
+ if (this.datasetId !== null) {
299
+ const data2 = await this._transport.get(`/datasets/${this.datasetId}`);
300
+ return Dataset2.fromDict(data2, this._transport);
301
+ }
261
302
  if (!this.hasDataset) return null;
262
303
  const data = await this._transport.get("/datasets", {
263
304
  job_id: this.id
264
305
  });
265
306
  const items = data["items"] ?? [];
266
307
  if (items.length === 0) return null;
267
- const { Dataset: Dataset2 } = await import("./dataset-PP755LIW.js");
268
308
  return Dataset2.fromDict(items[0], this._transport);
269
309
  }
310
+ /**
311
+ * Build a dataset from this job's execution.
312
+ *
313
+ * A completed data_process job does not have a dataset yet — building one
314
+ * is a separate, explicit step (`POST
315
+ * /datasets/build-from-execution/{executionId}`). Call this after
316
+ * `.wait()`, then `.wait()` again on the returned dataset_build Job before
317
+ * calling `.dataset()`:
318
+ *
319
+ * ```ts
320
+ * const job = await client.process("invoice.pdf");
321
+ * const done = await job.wait();
322
+ * const dataset = await (await done.buildDataset()).wait().then(j => j.dataset());
323
+ * ```
324
+ *
325
+ * @throws {Error} If this job has no executionId to build a dataset from
326
+ * (e.g. it failed, or is itself a dataset_build job).
327
+ */
328
+ async buildDataset(opts = {}) {
329
+ if (!this.executionId) {
330
+ throw new Error(
331
+ `Job ${this.id} has no executionId to build a dataset from (job must be a completed data_process job).`
332
+ );
333
+ }
334
+ const body = {
335
+ force_rebuild: opts.forceRebuild ?? false,
336
+ replace_existing: opts.replaceExisting ?? false
337
+ };
338
+ if (opts.name !== void 0) body["name"] = opts.name;
339
+ if (opts.description !== void 0) body["description"] = opts.description;
340
+ if (opts.slug !== void 0) body["slug"] = opts.slug;
341
+ const data = await this._transport.post(
342
+ `/datasets/build-from-execution/${this.executionId}`,
343
+ body
344
+ );
345
+ return _Job.fromDict(data, this._transport);
346
+ }
270
347
  toString() {
271
348
  return `Job(id=${this.id}, status=${this.status}, grade=${this.qualityGrade})`;
272
349
  }
@@ -310,6 +387,15 @@ var SearchResult = class _SearchResult {
310
387
  };
311
388
 
312
389
  // src/resources/jobs.ts
390
+ var VALID_RATINGS = /* @__PURE__ */ new Set(["up", "down"]);
391
+ var VALID_ISSUES = /* @__PURE__ */ new Set([
392
+ "wrong_doc_type",
393
+ "missing_fields",
394
+ "wrong_values",
395
+ "pii_missed",
396
+ "pii_over_masked",
397
+ "other"
398
+ ]);
313
399
  var JobsResource = class {
314
400
  constructor(_t) {
315
401
  this._t = _t;
@@ -327,6 +413,34 @@ var JobsResource = class {
327
413
  const items = data["items"] ?? [];
328
414
  return items.map((item) => Job.fromDict(item, this._t));
329
415
  }
416
+ /**
417
+ * Submit user feedback for a completed job. Upsert — a second call for the
418
+ * same job replaces the previous feedback.
419
+ *
420
+ * @param rating "up" or "down".
421
+ * @param opts.issue When rating="down": "wrong_doc_type" | "missing_fields" |
422
+ * "wrong_values" | "pii_missed" | "pii_over_masked" | "other".
423
+ */
424
+ async submitFeedback(jobId, rating, opts = {}) {
425
+ if (!VALID_RATINGS.has(rating)) {
426
+ throw new Error(`Invalid rating "${rating}". Valid: ${[...VALID_RATINGS].join(", ")}`);
427
+ }
428
+ if (opts.issue !== void 0 && !VALID_ISSUES.has(opts.issue)) {
429
+ throw new Error(`Invalid issue "${opts.issue}". Valid: ${[...VALID_ISSUES].join(", ")}`);
430
+ }
431
+ const data = await this._t.post(`/jobs/${jobId}/feedback`, {
432
+ rating,
433
+ issue: opts.issue ?? null,
434
+ notes: opts.notes ?? null
435
+ });
436
+ return jobFeedbackFromDict(data);
437
+ }
438
+ /** Existing feedback for a job, or null if none was submitted. */
439
+ async getFeedback(jobId) {
440
+ const data = await this._t.get(`/jobs/${jobId}/feedback`);
441
+ if (!data) return null;
442
+ return jobFeedbackFromDict(data);
443
+ }
330
444
  };
331
445
 
332
446
  // src/resources/datasets.ts
@@ -343,10 +457,121 @@ var DatasetsResource = class {
343
457
  const params = {};
344
458
  if (opts.page !== void 0) params["page"] = String(opts.page);
345
459
  if (opts.pageSize !== void 0) params["page_size"] = String(opts.pageSize);
460
+ if (opts.status !== void 0) params["status"] = opts.status;
461
+ if (opts.sourceExecutionId !== void 0) params["source_execution_id"] = String(opts.sourceExecutionId);
462
+ if (opts.sourceDocumentId !== void 0) params["source_document_id"] = String(opts.sourceDocumentId);
463
+ if (opts.q !== void 0) params["q"] = opts.q;
346
464
  const data = await this._t.get("/datasets", params);
347
465
  const items = data["items"] ?? [];
348
466
  return items.map((item) => Dataset.fromDict(item, this._t));
349
467
  }
468
+ /**
469
+ * Build a dataset from a completed execution.
470
+ *
471
+ * Prefer `Job.buildDataset()` when you already have a Job object — this is
472
+ * the lower-level call for when you only have an executionId (e.g. from
473
+ * `Document.latestExecution`).
474
+ *
475
+ * @returns A dataset_build Job — call `.wait()` then `.dataset()`.
476
+ */
477
+ async buildFromExecution(executionId, opts = {}) {
478
+ const body = {
479
+ force_rebuild: opts.forceRebuild ?? false,
480
+ replace_existing: opts.replaceExisting ?? false
481
+ };
482
+ if (opts.name !== void 0) body["name"] = opts.name;
483
+ if (opts.description !== void 0) body["description"] = opts.description;
484
+ if (opts.slug !== void 0) body["slug"] = opts.slug;
485
+ const data = await this._t.post(
486
+ `/datasets/build-from-execution/${executionId}`,
487
+ body
488
+ );
489
+ return Job.fromDict(data, this._t);
490
+ }
491
+ };
492
+
493
+ // src/models/document.ts
494
+ var Document = class _Document {
495
+ id;
496
+ filename;
497
+ fileExt;
498
+ status;
499
+ storagePath;
500
+ createdAt;
501
+ processingCount;
502
+ latestExecution;
503
+ dataset;
504
+ processingHistory;
505
+ relatedDatasets;
506
+ _transport;
507
+ constructor(data) {
508
+ this.id = data.id;
509
+ this.filename = data.filename;
510
+ this.fileExt = data.fileExt;
511
+ this.status = data.status;
512
+ this.storagePath = data.storagePath;
513
+ this.createdAt = data.createdAt;
514
+ this.processingCount = data.processingCount;
515
+ this.latestExecution = data.latestExecution;
516
+ this.dataset = data.dataset;
517
+ this.processingHistory = data.processingHistory;
518
+ this.relatedDatasets = data.relatedDatasets;
519
+ this._transport = data._transport;
520
+ }
521
+ static fromDict(data, transport) {
522
+ return new _Document({
523
+ id: String(data["id"] ?? ""),
524
+ filename: String(data["filename"] ?? ""),
525
+ fileExt: String(data["file_ext"] ?? ""),
526
+ status: String(data["status"] ?? ""),
527
+ storagePath: String(data["storage_path"] ?? ""),
528
+ createdAt: String(data["created_at"] ?? ""),
529
+ processingCount: Number(data["processing_count"] ?? 0),
530
+ latestExecution: data["latest_execution"] ?? null,
531
+ dataset: data["dataset"] ?? null,
532
+ processingHistory: data["processing_history"] ?? [],
533
+ relatedDatasets: data["related_datasets"] ?? [],
534
+ _transport: transport
535
+ });
536
+ }
537
+ /**
538
+ * Re-queue this document through the processing pipeline.
539
+ *
540
+ * Raises (via the API): 400 DOCUMENT_FILE_NOT_AVAILABLE if the source file
541
+ * is no longer on disk, or 400 REPROCESS_NOT_SUPPORTED for
542
+ * connector-sourced (e.g. S3) documents.
543
+ */
544
+ async reprocess(pipelineConfig) {
545
+ const body = {};
546
+ if (pipelineConfig) body["pipeline_config"] = pipelineConfig;
547
+ const data = await this._transport.post(`/documents/${this.id}/reprocess`, body);
548
+ return Job.fromDict(data, this._transport);
549
+ }
550
+ toString() {
551
+ return `Document(id=${this.id}, filename=${this.filename}, status=${this.status})`;
552
+ }
553
+ };
554
+
555
+ // src/resources/documents.ts
556
+ var DocumentsResource = class {
557
+ constructor(_t) {
558
+ this._t = _t;
559
+ }
560
+ _t;
561
+ /** Fetch a single document, including processingHistory and relatedDatasets. */
562
+ async get(documentId) {
563
+ const data = await this._t.get(`/documents/${documentId}`);
564
+ return Document.fromDict(data, this._t);
565
+ }
566
+ /** List documents for the current tenant, newest first. */
567
+ async list(opts = {}) {
568
+ const params = {};
569
+ if (opts.page !== void 0) params["page"] = String(opts.page);
570
+ if (opts.pageSize !== void 0) params["page_size"] = String(opts.pageSize);
571
+ const data = await this._t.get("/documents", params);
572
+ const items = data["items"] ?? [];
573
+ return items.map((item) => Document.fromDict(item, this._t));
574
+ }
350
575
  };
351
576
 
352
577
  // src/resources/usage.ts
@@ -356,15 +581,51 @@ var UsageResource = class {
356
581
  }
357
582
  _t;
358
583
  async current() {
359
- const data = await this._t.get("/usage/current");
584
+ const data = await this._t.get("/usage") ?? {};
585
+ const trial = data["trial"] ?? {};
586
+ const usage = data["usage"] ?? {};
587
+ const credits = usage["credits"] ?? {};
588
+ return {
589
+ plan: String(data["plan"] ?? ""),
590
+ creditsUsed: Number(credits["used"] ?? 0),
591
+ creditsLimit: credits["limit"] ?? null,
592
+ creditsRemaining: credits["remaining"] ?? null,
593
+ isTrial: Boolean(trial["is_trial"] ?? false),
594
+ trialEndsAt: trial["trial_ends_at"] ?? null,
595
+ trialDaysRemaining: trial["trial_days_remaining"] ?? null
596
+ };
597
+ }
598
+ /** @param period "7d" | "30d" | "90d". Default: "30d". */
599
+ async history(period = "30d") {
600
+ const data = await this._t.get("/usage/history", { period });
601
+ return (data ?? []).map((item) => ({
602
+ date: String(item["date"] ?? ""),
603
+ creditsUsed: Number(item["credits_used"] ?? 0),
604
+ jobsCount: Number(item["jobs_count"] ?? 0)
605
+ }));
606
+ }
607
+ /** @param period "7d" | "30d" | "90d". Default: "30d". */
608
+ async qualityTrend(period = "30d") {
609
+ const data = await this._t.get("/usage/quality-trend", { period });
610
+ return (data ?? []).map((item) => ({
611
+ date: String(item["date"] ?? ""),
612
+ avgQualityScore: Number(item["avg_quality_score"] ?? 0),
613
+ gradeDistribution: item["grade_distribution"] ?? {},
614
+ avgFieldFillRate: item["avg_field_fill_rate"] ?? null,
615
+ jobCount: Number(item["job_count"] ?? 0)
616
+ }));
617
+ }
618
+ /** Current rate limit configuration and window usage. Does not consume a request slot. */
619
+ async rateLimits() {
620
+ const data = await this._t.get("/usage/rate-limits") ?? {};
360
621
  return {
361
622
  plan: String(data["plan"] ?? ""),
362
- creditsUsed: Number(data["credits_used"] ?? 0),
363
- creditsLimit: Number(data["credits_limit"] ?? 0),
364
- creditsRemaining: Number(data["credits_remaining"] ?? 0),
365
- resetAt: String(data["reset_at"] ?? ""),
366
- periodStart: String(data["period_start"] ?? ""),
367
- periodEnd: String(data["period_end"] ?? "")
623
+ unlimited: Boolean(data["unlimited"] ?? false),
624
+ limit: data["limit"] ?? null,
625
+ used: data["used"] ?? null,
626
+ remaining: data["remaining"] ?? null,
627
+ windowSeconds: Number(data["window_seconds"] ?? 0),
628
+ resetInSeconds: data["reset_in_seconds"] ?? null
368
629
  };
369
630
  }
370
631
  };
@@ -436,6 +697,32 @@ var Connector = class _Connector {
436
697
  return `Connector(id=${this.id}, name=${this.name}, type=${this.type})`;
437
698
  }
438
699
  };
700
+ function syncScheduleFromDict(data) {
701
+ return {
702
+ id: String(data["id"] ?? ""),
703
+ connectorId: String(data["connector_id"] ?? ""),
704
+ cronExpression: String(data["cron_expression"] ?? ""),
705
+ prefixFilter: data["prefix_filter"] ?? null,
706
+ isActive: Boolean(data["is_active"] ?? true),
707
+ lastRunAt: data["last_run_at"] ?? null,
708
+ nextRunAt: data["next_run_at"] ?? null,
709
+ createdAt: String(data["created_at"] ?? "")
710
+ };
711
+ }
712
+ function syncLogFromDict(data) {
713
+ return {
714
+ id: String(data["id"] ?? ""),
715
+ scheduleId: String(data["schedule_id"] ?? ""),
716
+ startedAt: String(data["started_at"] ?? ""),
717
+ completedAt: data["completed_at"] ?? null,
718
+ filesFound: Number(data["files_found"] ?? 0),
719
+ filesNew: Number(data["files_new"] ?? 0),
720
+ filesSkipped: Number(data["files_skipped"] ?? 0),
721
+ filesFailed: Number(data["files_failed"] ?? 0),
722
+ status: String(data["status"] ?? ""),
723
+ errorMessage: data["error_message"] ?? null
724
+ };
725
+ }
439
726
 
440
727
  // src/resources/connectors.ts
441
728
  var VALID_TYPES = /* @__PURE__ */ new Set([
@@ -479,13 +766,54 @@ var ConnectorsResource = class {
479
766
  message: String(data["message"] ?? "")
480
767
  };
481
768
  }
769
+ /** Define a scheduled sync for a connector (Pro+ required). */
770
+ async createSchedule(connectorId, cronExpression, prefixFilter = null) {
771
+ const data = await this._t.post(`/connectors/${connectorId}/schedules`, {
772
+ cron_expression: cronExpression,
773
+ prefix_filter: prefixFilter
774
+ });
775
+ return syncScheduleFromDict(data);
776
+ }
777
+ /** Active schedules for a connector. */
778
+ async listSchedules(connectorId) {
779
+ const data = await this._t.get(`/connectors/${connectorId}/schedules`);
780
+ return (data ?? []).map(syncScheduleFromDict);
781
+ }
782
+ /** Delete a scheduled sync. */
783
+ async deleteSchedule(connectorId, scheduleId) {
784
+ await this._t.delete(`/connectors/${connectorId}/schedules/${scheduleId}`);
785
+ }
786
+ /** Run a schedule immediately instead of waiting for its cron time. */
787
+ async triggerSchedule(connectorId, scheduleId) {
788
+ const data = await this._t.post(
789
+ `/connectors/${connectorId}/schedules/${scheduleId}/trigger`
790
+ );
791
+ return syncLogFromDict(data);
792
+ }
793
+ /** Recent sync run logs for a schedule. */
794
+ async scheduleLogs(connectorId, scheduleId) {
795
+ const data = await this._t.get(
796
+ `/connectors/${connectorId}/schedules/${scheduleId}/logs`
797
+ );
798
+ return (data ?? []).map(syncLogFromDict);
799
+ }
482
800
  };
483
801
 
484
802
  // src/client.ts
485
803
  var DEFAULT_BASE_URL = "https://api.flexorch.com/v1";
804
+ function firstJobOrThrow(uploadResponse, filename, transport) {
805
+ const jobs = uploadResponse["jobs"] ?? [];
806
+ if (jobs.length > 0) {
807
+ return Job.fromDict(jobs[0], transport);
808
+ }
809
+ const rejected = uploadResponse["rejected"] ?? [];
810
+ const reason = rejected.length > 0 ? String(rejected[0]["error"]) : "unknown error";
811
+ throw new ValidationError(`${filename} was rejected: ${reason}`);
812
+ }
486
813
  var FlexOrchClient = class {
487
814
  jobs;
488
815
  datasets;
816
+ documents;
489
817
  usage;
490
818
  webhooks;
491
819
  connectors;
@@ -507,6 +835,7 @@ var FlexOrchClient = class {
507
835
  );
508
836
  this.jobs = new JobsResource(this._transport);
509
837
  this.datasets = new DatasetsResource(this._transport);
838
+ this.documents = new DocumentsResource(this._transport);
510
839
  this.usage = new UsageResource(this._transport);
511
840
  this.webhooks = new WebhooksResource(this._transport);
512
841
  this.connectors = new ConnectorsResource(this._transport);
@@ -524,13 +853,13 @@ var FlexOrchClient = class {
524
853
  chunks.push(chunk);
525
854
  }
526
855
  const blob = new Blob([Buffer.concat(chunks)], { type: "application/octet-stream" });
527
- form.append("file", blob, basename(filePath));
856
+ form.append("files", blob, basename(filePath));
528
857
  form.append("locale", opts.locale ?? "und");
529
858
  if (opts.pipelineConfig) {
530
859
  form.append("pipeline_config", JSON.stringify(opts.pipelineConfig));
531
860
  }
532
861
  const data = await this._transport.postForm("/data-process/async", form);
533
- return Job.fromDict(data, this._transport);
862
+ return firstJobOrThrow(data, basename(filePath), this._transport);
534
863
  }
535
864
  async processMany(filePaths, opts = {}) {
536
865
  const jobs = [];
@@ -549,7 +878,7 @@ var FlexOrchClient = class {
549
878
  form.append("pipeline_config", JSON.stringify(opts.pipelineConfig));
550
879
  }
551
880
  const data = await this._transport.postForm("/data-process/async", form);
552
- jobs.push(Job.fromDict(data, this._transport));
881
+ jobs.push(firstJobOrThrow(data, key, this._transport));
553
882
  }
554
883
  return jobs;
555
884
  }
@@ -753,11 +1082,12 @@ var FlexOrchReader = class {
753
1082
  };
754
1083
 
755
1084
  // src/index.ts
756
- var version = "0.2.3";
1085
+ var version = "0.3.1";
757
1086
  export {
758
1087
  AuthError,
759
1088
  Connector,
760
1089
  Dataset,
1090
+ Document,
761
1091
  FlexOrchClient,
762
1092
  FlexOrchError,
763
1093
  FlexOrchReader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flexorch-sdk",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "description": "TypeScript/JavaScript SDK for the FlexOrch API — process documents, build LLM-ready datasets",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",