flexorch-sdk 0.2.3 → 0.3.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/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.0"
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,12 +197,24 @@ 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;
201
219
  /**
202
220
  * True when the underlying pipeline execution completed but one or more
@@ -216,21 +234,29 @@ var Job = class _Job {
216
234
  this.qualityGrade = data.qualityGrade;
217
235
  this.qualityScore = data.qualityScore;
218
236
  this.documentId = data.documentId;
237
+ this.executionId = data.executionId;
219
238
  this.hasDataset = data.hasDataset;
220
239
  this.degraded = data.degraded;
221
240
  this.failureReason = data.failureReason;
222
241
  this._transport = data._transport;
223
242
  }
224
243
  static fromDict(data, transport) {
225
- const quality = data["quality"] ?? {};
226
244
  const executionSummary = data["execution_summary"];
245
+ const processingSummary = data["processing_summary"];
246
+ let quality = data["quality"];
247
+ if (!quality && processingSummary) {
248
+ quality = processingSummary["quality"];
249
+ }
250
+ quality = quality ?? {};
251
+ const executionId = executionSummary?.["execution_id"] ?? processingSummary?.["execution_id"] ?? data["execution_id"] ?? null;
227
252
  return new _Job({
228
253
  id: String(data["job_id"] ?? data["id"] ?? ""),
229
254
  status: String(data["status"] ?? ""),
230
255
  qualityGrade: quality["grade"] ?? null,
231
256
  qualityScore: quality["score"] ?? null,
232
257
  documentId: data["document_id"] ?? null,
233
- hasDataset: Boolean(data["has_dataset"] ?? false),
258
+ executionId,
259
+ hasDataset: Boolean(data["has_dataset"] ?? processingSummary?.["has_dataset"] ?? false),
234
260
  degraded: Boolean(executionSummary?.["degraded"] ?? false),
235
261
  failureReason: data["failure_reason"] ?? null,
236
262
  _transport: transport
@@ -264,9 +290,46 @@ var Job = class _Job {
264
290
  });
265
291
  const items = data["items"] ?? [];
266
292
  if (items.length === 0) return null;
267
- const { Dataset: Dataset2 } = await import("./dataset-PP755LIW.js");
293
+ const { Dataset: Dataset2 } = await import("./dataset-E7EXJETI.js");
268
294
  return Dataset2.fromDict(items[0], this._transport);
269
295
  }
296
+ /**
297
+ * Build a dataset from this job's execution.
298
+ *
299
+ * A completed data_process job does not have a dataset yet — building one
300
+ * is a separate, explicit step (`POST
301
+ * /datasets/build-from-execution/{executionId}`). Call this after
302
+ * `.wait()`, then `.wait()` again on the returned dataset_build Job before
303
+ * calling `.dataset()`:
304
+ *
305
+ * ```ts
306
+ * const job = await client.process("invoice.pdf");
307
+ * const done = await job.wait();
308
+ * const dataset = await (await done.buildDataset()).wait().then(j => j.dataset());
309
+ * ```
310
+ *
311
+ * @throws {Error} If this job has no executionId to build a dataset from
312
+ * (e.g. it failed, or is itself a dataset_build job).
313
+ */
314
+ async buildDataset(opts = {}) {
315
+ if (!this.executionId) {
316
+ throw new Error(
317
+ `Job ${this.id} has no executionId to build a dataset from (job must be a completed data_process job).`
318
+ );
319
+ }
320
+ const body = {
321
+ force_rebuild: opts.forceRebuild ?? false,
322
+ replace_existing: opts.replaceExisting ?? false
323
+ };
324
+ if (opts.name !== void 0) body["name"] = opts.name;
325
+ if (opts.description !== void 0) body["description"] = opts.description;
326
+ if (opts.slug !== void 0) body["slug"] = opts.slug;
327
+ const data = await this._transport.post(
328
+ `/datasets/build-from-execution/${this.executionId}`,
329
+ body
330
+ );
331
+ return _Job.fromDict(data, this._transport);
332
+ }
270
333
  toString() {
271
334
  return `Job(id=${this.id}, status=${this.status}, grade=${this.qualityGrade})`;
272
335
  }
@@ -310,6 +373,15 @@ var SearchResult = class _SearchResult {
310
373
  };
311
374
 
312
375
  // src/resources/jobs.ts
376
+ var VALID_RATINGS = /* @__PURE__ */ new Set(["up", "down"]);
377
+ var VALID_ISSUES = /* @__PURE__ */ new Set([
378
+ "wrong_doc_type",
379
+ "missing_fields",
380
+ "wrong_values",
381
+ "pii_missed",
382
+ "pii_over_masked",
383
+ "other"
384
+ ]);
313
385
  var JobsResource = class {
314
386
  constructor(_t) {
315
387
  this._t = _t;
@@ -327,6 +399,34 @@ var JobsResource = class {
327
399
  const items = data["items"] ?? [];
328
400
  return items.map((item) => Job.fromDict(item, this._t));
329
401
  }
402
+ /**
403
+ * Submit user feedback for a completed job. Upsert — a second call for the
404
+ * same job replaces the previous feedback.
405
+ *
406
+ * @param rating "up" or "down".
407
+ * @param opts.issue When rating="down": "wrong_doc_type" | "missing_fields" |
408
+ * "wrong_values" | "pii_missed" | "pii_over_masked" | "other".
409
+ */
410
+ async submitFeedback(jobId, rating, opts = {}) {
411
+ if (!VALID_RATINGS.has(rating)) {
412
+ throw new Error(`Invalid rating "${rating}". Valid: ${[...VALID_RATINGS].join(", ")}`);
413
+ }
414
+ if (opts.issue !== void 0 && !VALID_ISSUES.has(opts.issue)) {
415
+ throw new Error(`Invalid issue "${opts.issue}". Valid: ${[...VALID_ISSUES].join(", ")}`);
416
+ }
417
+ const data = await this._t.post(`/jobs/${jobId}/feedback`, {
418
+ rating,
419
+ issue: opts.issue ?? null,
420
+ notes: opts.notes ?? null
421
+ });
422
+ return jobFeedbackFromDict(data);
423
+ }
424
+ /** Existing feedback for a job, or null if none was submitted. */
425
+ async getFeedback(jobId) {
426
+ const data = await this._t.get(`/jobs/${jobId}/feedback`);
427
+ if (!data) return null;
428
+ return jobFeedbackFromDict(data);
429
+ }
330
430
  };
331
431
 
332
432
  // src/resources/datasets.ts
@@ -343,10 +443,121 @@ var DatasetsResource = class {
343
443
  const params = {};
344
444
  if (opts.page !== void 0) params["page"] = String(opts.page);
345
445
  if (opts.pageSize !== void 0) params["page_size"] = String(opts.pageSize);
446
+ if (opts.status !== void 0) params["status"] = opts.status;
447
+ if (opts.sourceExecutionId !== void 0) params["source_execution_id"] = String(opts.sourceExecutionId);
448
+ if (opts.sourceDocumentId !== void 0) params["source_document_id"] = String(opts.sourceDocumentId);
449
+ if (opts.q !== void 0) params["q"] = opts.q;
346
450
  const data = await this._t.get("/datasets", params);
347
451
  const items = data["items"] ?? [];
348
452
  return items.map((item) => Dataset.fromDict(item, this._t));
349
453
  }
454
+ /**
455
+ * Build a dataset from a completed execution.
456
+ *
457
+ * Prefer `Job.buildDataset()` when you already have a Job object — this is
458
+ * the lower-level call for when you only have an executionId (e.g. from
459
+ * `Document.latestExecution`).
460
+ *
461
+ * @returns A dataset_build Job — call `.wait()` then `.dataset()`.
462
+ */
463
+ async buildFromExecution(executionId, opts = {}) {
464
+ const body = {
465
+ force_rebuild: opts.forceRebuild ?? false,
466
+ replace_existing: opts.replaceExisting ?? false
467
+ };
468
+ if (opts.name !== void 0) body["name"] = opts.name;
469
+ if (opts.description !== void 0) body["description"] = opts.description;
470
+ if (opts.slug !== void 0) body["slug"] = opts.slug;
471
+ const data = await this._t.post(
472
+ `/datasets/build-from-execution/${executionId}`,
473
+ body
474
+ );
475
+ return Job.fromDict(data, this._t);
476
+ }
477
+ };
478
+
479
+ // src/models/document.ts
480
+ var Document = class _Document {
481
+ id;
482
+ filename;
483
+ fileExt;
484
+ status;
485
+ storagePath;
486
+ createdAt;
487
+ processingCount;
488
+ latestExecution;
489
+ dataset;
490
+ processingHistory;
491
+ relatedDatasets;
492
+ _transport;
493
+ constructor(data) {
494
+ this.id = data.id;
495
+ this.filename = data.filename;
496
+ this.fileExt = data.fileExt;
497
+ this.status = data.status;
498
+ this.storagePath = data.storagePath;
499
+ this.createdAt = data.createdAt;
500
+ this.processingCount = data.processingCount;
501
+ this.latestExecution = data.latestExecution;
502
+ this.dataset = data.dataset;
503
+ this.processingHistory = data.processingHistory;
504
+ this.relatedDatasets = data.relatedDatasets;
505
+ this._transport = data._transport;
506
+ }
507
+ static fromDict(data, transport) {
508
+ return new _Document({
509
+ id: String(data["id"] ?? ""),
510
+ filename: String(data["filename"] ?? ""),
511
+ fileExt: String(data["file_ext"] ?? ""),
512
+ status: String(data["status"] ?? ""),
513
+ storagePath: String(data["storage_path"] ?? ""),
514
+ createdAt: String(data["created_at"] ?? ""),
515
+ processingCount: Number(data["processing_count"] ?? 0),
516
+ latestExecution: data["latest_execution"] ?? null,
517
+ dataset: data["dataset"] ?? null,
518
+ processingHistory: data["processing_history"] ?? [],
519
+ relatedDatasets: data["related_datasets"] ?? [],
520
+ _transport: transport
521
+ });
522
+ }
523
+ /**
524
+ * Re-queue this document through the processing pipeline.
525
+ *
526
+ * Raises (via the API): 400 DOCUMENT_FILE_NOT_AVAILABLE if the source file
527
+ * is no longer on disk, or 400 REPROCESS_NOT_SUPPORTED for
528
+ * connector-sourced (e.g. S3) documents.
529
+ */
530
+ async reprocess(pipelineConfig) {
531
+ const body = {};
532
+ if (pipelineConfig) body["pipeline_config"] = pipelineConfig;
533
+ const data = await this._transport.post(`/documents/${this.id}/reprocess`, body);
534
+ return Job.fromDict(data, this._transport);
535
+ }
536
+ toString() {
537
+ return `Document(id=${this.id}, filename=${this.filename}, status=${this.status})`;
538
+ }
539
+ };
540
+
541
+ // src/resources/documents.ts
542
+ var DocumentsResource = class {
543
+ constructor(_t) {
544
+ this._t = _t;
545
+ }
546
+ _t;
547
+ /** Fetch a single document, including processingHistory and relatedDatasets. */
548
+ async get(documentId) {
549
+ const data = await this._t.get(`/documents/${documentId}`);
550
+ return Document.fromDict(data, this._t);
551
+ }
552
+ /** List documents for the current tenant, newest first. */
553
+ async list(opts = {}) {
554
+ const params = {};
555
+ if (opts.page !== void 0) params["page"] = String(opts.page);
556
+ if (opts.pageSize !== void 0) params["page_size"] = String(opts.pageSize);
557
+ const data = await this._t.get("/documents", params);
558
+ const items = data["items"] ?? [];
559
+ return items.map((item) => Document.fromDict(item, this._t));
560
+ }
350
561
  };
351
562
 
352
563
  // src/resources/usage.ts
@@ -356,15 +567,51 @@ var UsageResource = class {
356
567
  }
357
568
  _t;
358
569
  async current() {
359
- const data = await this._t.get("/usage/current");
570
+ const data = await this._t.get("/usage") ?? {};
571
+ const trial = data["trial"] ?? {};
572
+ const usage = data["usage"] ?? {};
573
+ const credits = usage["credits"] ?? {};
360
574
  return {
361
575
  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"] ?? "")
576
+ creditsUsed: Number(credits["used"] ?? 0),
577
+ creditsLimit: credits["limit"] ?? null,
578
+ creditsRemaining: credits["remaining"] ?? null,
579
+ isTrial: Boolean(trial["is_trial"] ?? false),
580
+ trialEndsAt: trial["trial_ends_at"] ?? null,
581
+ trialDaysRemaining: trial["trial_days_remaining"] ?? null
582
+ };
583
+ }
584
+ /** @param period "7d" | "30d" | "90d". Default: "30d". */
585
+ async history(period = "30d") {
586
+ const data = await this._t.get("/usage/history", { period });
587
+ return (data ?? []).map((item) => ({
588
+ date: String(item["date"] ?? ""),
589
+ creditsUsed: Number(item["credits_used"] ?? 0),
590
+ jobsCount: Number(item["jobs_count"] ?? 0)
591
+ }));
592
+ }
593
+ /** @param period "7d" | "30d" | "90d". Default: "30d". */
594
+ async qualityTrend(period = "30d") {
595
+ const data = await this._t.get("/usage/quality-trend", { period });
596
+ return (data ?? []).map((item) => ({
597
+ date: String(item["date"] ?? ""),
598
+ avgQualityScore: Number(item["avg_quality_score"] ?? 0),
599
+ gradeDistribution: item["grade_distribution"] ?? {},
600
+ avgFieldFillRate: item["avg_field_fill_rate"] ?? null,
601
+ jobCount: Number(item["job_count"] ?? 0)
602
+ }));
603
+ }
604
+ /** Current rate limit configuration and window usage. Does not consume a request slot. */
605
+ async rateLimits() {
606
+ const data = await this._t.get("/usage/rate-limits") ?? {};
607
+ return {
608
+ plan: String(data["plan"] ?? ""),
609
+ unlimited: Boolean(data["unlimited"] ?? false),
610
+ limit: data["limit"] ?? null,
611
+ used: data["used"] ?? null,
612
+ remaining: data["remaining"] ?? null,
613
+ windowSeconds: Number(data["window_seconds"] ?? 0),
614
+ resetInSeconds: data["reset_in_seconds"] ?? null
368
615
  };
369
616
  }
370
617
  };
@@ -436,6 +683,32 @@ var Connector = class _Connector {
436
683
  return `Connector(id=${this.id}, name=${this.name}, type=${this.type})`;
437
684
  }
438
685
  };
686
+ function syncScheduleFromDict(data) {
687
+ return {
688
+ id: String(data["id"] ?? ""),
689
+ connectorId: String(data["connector_id"] ?? ""),
690
+ cronExpression: String(data["cron_expression"] ?? ""),
691
+ prefixFilter: data["prefix_filter"] ?? null,
692
+ isActive: Boolean(data["is_active"] ?? true),
693
+ lastRunAt: data["last_run_at"] ?? null,
694
+ nextRunAt: data["next_run_at"] ?? null,
695
+ createdAt: String(data["created_at"] ?? "")
696
+ };
697
+ }
698
+ function syncLogFromDict(data) {
699
+ return {
700
+ id: String(data["id"] ?? ""),
701
+ scheduleId: String(data["schedule_id"] ?? ""),
702
+ startedAt: String(data["started_at"] ?? ""),
703
+ completedAt: data["completed_at"] ?? null,
704
+ filesFound: Number(data["files_found"] ?? 0),
705
+ filesNew: Number(data["files_new"] ?? 0),
706
+ filesSkipped: Number(data["files_skipped"] ?? 0),
707
+ filesFailed: Number(data["files_failed"] ?? 0),
708
+ status: String(data["status"] ?? ""),
709
+ errorMessage: data["error_message"] ?? null
710
+ };
711
+ }
439
712
 
440
713
  // src/resources/connectors.ts
441
714
  var VALID_TYPES = /* @__PURE__ */ new Set([
@@ -479,13 +752,54 @@ var ConnectorsResource = class {
479
752
  message: String(data["message"] ?? "")
480
753
  };
481
754
  }
755
+ /** Define a scheduled sync for a connector (Pro+ required). */
756
+ async createSchedule(connectorId, cronExpression, prefixFilter = null) {
757
+ const data = await this._t.post(`/connectors/${connectorId}/schedules`, {
758
+ cron_expression: cronExpression,
759
+ prefix_filter: prefixFilter
760
+ });
761
+ return syncScheduleFromDict(data);
762
+ }
763
+ /** Active schedules for a connector. */
764
+ async listSchedules(connectorId) {
765
+ const data = await this._t.get(`/connectors/${connectorId}/schedules`);
766
+ return (data ?? []).map(syncScheduleFromDict);
767
+ }
768
+ /** Delete a scheduled sync. */
769
+ async deleteSchedule(connectorId, scheduleId) {
770
+ await this._t.delete(`/connectors/${connectorId}/schedules/${scheduleId}`);
771
+ }
772
+ /** Run a schedule immediately instead of waiting for its cron time. */
773
+ async triggerSchedule(connectorId, scheduleId) {
774
+ const data = await this._t.post(
775
+ `/connectors/${connectorId}/schedules/${scheduleId}/trigger`
776
+ );
777
+ return syncLogFromDict(data);
778
+ }
779
+ /** Recent sync run logs for a schedule. */
780
+ async scheduleLogs(connectorId, scheduleId) {
781
+ const data = await this._t.get(
782
+ `/connectors/${connectorId}/schedules/${scheduleId}/logs`
783
+ );
784
+ return (data ?? []).map(syncLogFromDict);
785
+ }
482
786
  };
483
787
 
484
788
  // src/client.ts
485
789
  var DEFAULT_BASE_URL = "https://api.flexorch.com/v1";
790
+ function firstJobOrThrow(uploadResponse, filename, transport) {
791
+ const jobs = uploadResponse["jobs"] ?? [];
792
+ if (jobs.length > 0) {
793
+ return Job.fromDict(jobs[0], transport);
794
+ }
795
+ const rejected = uploadResponse["rejected"] ?? [];
796
+ const reason = rejected.length > 0 ? String(rejected[0]["error"]) : "unknown error";
797
+ throw new ValidationError(`${filename} was rejected: ${reason}`);
798
+ }
486
799
  var FlexOrchClient = class {
487
800
  jobs;
488
801
  datasets;
802
+ documents;
489
803
  usage;
490
804
  webhooks;
491
805
  connectors;
@@ -507,6 +821,7 @@ var FlexOrchClient = class {
507
821
  );
508
822
  this.jobs = new JobsResource(this._transport);
509
823
  this.datasets = new DatasetsResource(this._transport);
824
+ this.documents = new DocumentsResource(this._transport);
510
825
  this.usage = new UsageResource(this._transport);
511
826
  this.webhooks = new WebhooksResource(this._transport);
512
827
  this.connectors = new ConnectorsResource(this._transport);
@@ -524,13 +839,13 @@ var FlexOrchClient = class {
524
839
  chunks.push(chunk);
525
840
  }
526
841
  const blob = new Blob([Buffer.concat(chunks)], { type: "application/octet-stream" });
527
- form.append("file", blob, basename(filePath));
842
+ form.append("files", blob, basename(filePath));
528
843
  form.append("locale", opts.locale ?? "und");
529
844
  if (opts.pipelineConfig) {
530
845
  form.append("pipeline_config", JSON.stringify(opts.pipelineConfig));
531
846
  }
532
847
  const data = await this._transport.postForm("/data-process/async", form);
533
- return Job.fromDict(data, this._transport);
848
+ return firstJobOrThrow(data, basename(filePath), this._transport);
534
849
  }
535
850
  async processMany(filePaths, opts = {}) {
536
851
  const jobs = [];
@@ -549,7 +864,7 @@ var FlexOrchClient = class {
549
864
  form.append("pipeline_config", JSON.stringify(opts.pipelineConfig));
550
865
  }
551
866
  const data = await this._transport.postForm("/data-process/async", form);
552
- jobs.push(Job.fromDict(data, this._transport));
867
+ jobs.push(firstJobOrThrow(data, key, this._transport));
553
868
  }
554
869
  return jobs;
555
870
  }
@@ -753,11 +1068,12 @@ var FlexOrchReader = class {
753
1068
  };
754
1069
 
755
1070
  // src/index.ts
756
- var version = "0.2.3";
1071
+ var version = "0.3.0";
757
1072
  export {
758
1073
  AuthError,
759
1074
  Connector,
760
1075
  Dataset,
1076
+ Document,
761
1077
  FlexOrchClient,
762
1078
  FlexOrchError,
763
1079
  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.0",
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",