@objectstack/rest 12.5.0 → 12.6.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.cjs CHANGED
@@ -38,7 +38,7 @@ __export(index_exports, {
38
38
  module.exports = __toCommonJS(index_exports);
39
39
 
40
40
  // src/rest-server.ts
41
- var import_core = require("@objectstack/core");
41
+ var import_core2 = require("@objectstack/core");
42
42
 
43
43
  // src/route-manager.ts
44
44
  var RouteManager = class {
@@ -558,6 +558,17 @@ async function coerceRow(rawRow, metaMap, ctx) {
558
558
  }
559
559
 
560
560
  // src/import-runner.ts
561
+ var import_core = require("@objectstack/core");
562
+ function extractRecordId(rec) {
563
+ const id = rec?.id ?? rec?.record?.id;
564
+ return id != null ? String(id) : void 0;
565
+ }
566
+ function toFailedResult(rowNo, err) {
567
+ const code = err?.code ?? "IMPORT_ROW_FAILED";
568
+ const message = typeof err?.message === "string" ? err.message.slice(0, 300) : "Row failed";
569
+ return { row: rowNo, ok: false, action: "failed", error: message, code };
570
+ }
571
+ var MAX_CREATE_BATCH_SIZE = 200;
561
572
  function runImport(opts) {
562
573
  const {
563
574
  p,
@@ -645,7 +656,7 @@ function runImport(opts) {
645
656
  return recs[0];
646
657
  };
647
658
  const writeCtx = { ...context ?? {}, skipAutomations: !runAutomations };
648
- const results = [];
659
+ const results = new Array(rows.length);
649
660
  let okCount = 0, errCount = 0, created = 0, updated = 0, skipped = 0;
650
661
  let cancelled = false;
651
662
  const snapshot = (processed) => ({
@@ -656,6 +667,47 @@ function runImport(opts) {
656
667
  skipped,
657
668
  errors: errCount
658
669
  });
670
+ const canBulkCreate = typeof p.createManyData === "function";
671
+ const pendingCreates = [];
672
+ const flushPendingCreates = async () => {
673
+ if (pendingCreates.length === 0) return;
674
+ const batch = pendingCreates.splice(0, pendingCreates.length);
675
+ const writeResults = await (0, import_core.bulkWrite)(
676
+ batch.map((b) => b.data),
677
+ {
678
+ // Flush cadence follows progressEvery, but the write batch itself is
679
+ // capped independently — a caller-supplied progressEvery far above
680
+ // the issue's suggested 100-500 rows/batch must not translate into
681
+ // one oversized multi-row INSERT statement.
682
+ batchSize: Math.min(progressEvery, MAX_CREATE_BATCH_SIZE),
683
+ writeBatch: (chunk) => p.createManyData({
684
+ object: objectName,
685
+ records: chunk,
686
+ context: writeCtx,
687
+ ...environmentId ? { environmentId } : {}
688
+ }).then((r) => r.records),
689
+ writeOne: (row) => p.createData({
690
+ object: objectName,
691
+ data: row,
692
+ context: writeCtx,
693
+ ...environmentId ? { environmentId } : {}
694
+ })
695
+ }
696
+ );
697
+ for (const res of writeResults) {
698
+ const { index, rowNo } = batch[res.index];
699
+ if (res.ok) {
700
+ const id = extractRecordId(res.record);
701
+ okCount++;
702
+ created++;
703
+ if (collectUndo && id != null) undoLog.created.push(id);
704
+ results[index] = { row: rowNo, ok: true, action: "created", id };
705
+ } else {
706
+ errCount++;
707
+ results[index] = toFailedResult(rowNo, res.error);
708
+ }
709
+ }
710
+ };
659
711
  return (async () => {
660
712
  for (let i = 0; i < rows.length; i++) {
661
713
  const rowNo = i + 1;
@@ -669,7 +721,7 @@ function runImport(opts) {
669
721
  if (errors.length > 0) {
670
722
  const first2 = errors[0];
671
723
  errCount++;
672
- results.push({ row: rowNo, ok: false, action: "failed", field: first2.field, code: first2.code, error: first2.message });
724
+ results[i] = { row: rowNo, ok: false, action: "failed", field: first2.field, code: first2.code, error: first2.message };
673
725
  } else {
674
726
  let existing = "none";
675
727
  let handled = false;
@@ -677,11 +729,11 @@ function runImport(opts) {
677
729
  existing = await findExisting(data);
678
730
  if (existing === "ambiguous") {
679
731
  errCount++;
680
- results.push({ row: rowNo, ok: false, action: "failed", code: "AMBIGUOUS_MATCH", error: `matchFields matched more than one ${objectName} record` });
732
+ results[i] = { row: rowNo, ok: false, action: "failed", code: "AMBIGUOUS_MATCH", error: `matchFields matched more than one ${objectName} record` };
681
733
  handled = true;
682
734
  } else if (existing === "blank" && (skipBlankMatchKey || writeMode === "update")) {
683
735
  skipped++;
684
- results.push({ row: rowNo, ok: true, action: "skipped", code: "BLANK_MATCH_KEY" });
736
+ results[i] = { row: rowNo, ok: true, action: "skipped", code: "BLANK_MATCH_KEY" };
685
737
  handled = true;
686
738
  }
687
739
  }
@@ -690,45 +742,46 @@ function runImport(opts) {
690
742
  const willCreate = !willUpdate && (writeMode === "insert" || writeMode === "upsert");
691
743
  if (!willUpdate && !willCreate) {
692
744
  skipped++;
693
- results.push({ row: rowNo, ok: true, action: "skipped", code: "NO_MATCH" });
745
+ results[i] = { row: rowNo, ok: true, action: "skipped", code: "NO_MATCH" };
694
746
  } else if (dryRun) {
695
747
  okCount++;
696
748
  if (willUpdate) {
697
749
  updated++;
698
- results.push({ row: rowNo, ok: true, action: "updated", id: String(existing.id ?? "") || void 0 });
750
+ results[i] = { row: rowNo, ok: true, action: "updated", id: String(existing.id ?? "") || void 0 };
699
751
  } else {
700
752
  created++;
701
- results.push({ row: rowNo, ok: true, action: "created" });
753
+ results[i] = { row: rowNo, ok: true, action: "created" };
702
754
  }
703
755
  } else if (willUpdate) {
704
756
  const target = existing;
705
- const res2 = await p.updateData({ object: objectName, id: target.id, data, context: writeCtx, ...environmentId ? { environmentId } : {} });
706
- const id = res2?.id ?? res2?.record?.id ?? target.id;
757
+ const res2 = await (0, import_core.withTransientRetry)(() => p.updateData({ object: objectName, id: target.id, data, context: writeCtx, ...environmentId ? { environmentId } : {} }));
758
+ const id = extractRecordId(res2) ?? String(target.id);
707
759
  okCount++;
708
760
  updated++;
709
761
  if (collectUndo && target.id != null) {
710
762
  undoLog.updated.push({ id: String(target.id), before: captureBefore(target, data) });
711
763
  }
712
- results.push({ row: rowNo, ok: true, action: "updated", id: id != null ? String(id) : void 0 });
764
+ results[i] = { row: rowNo, ok: true, action: "updated", id };
765
+ } else if (canBulkCreate) {
766
+ pendingCreates.push({ index: i, rowNo, data });
713
767
  } else {
714
768
  const res2 = await p.createData({ object: objectName, data, context: writeCtx, ...environmentId ? { environmentId } : {} });
715
- const id = res2?.id ?? res2?.record?.id;
769
+ const id = extractRecordId(res2);
716
770
  okCount++;
717
771
  created++;
718
- if (collectUndo && id != null) undoLog.created.push(String(id));
719
- results.push({ row: rowNo, ok: true, action: "created", id: id != null ? String(id) : void 0 });
772
+ if (collectUndo && id != null) undoLog.created.push(id);
773
+ results[i] = { row: rowNo, ok: true, action: "created", id };
720
774
  }
721
775
  }
722
776
  }
723
777
  } catch (err) {
724
778
  errCount++;
725
- const code = err?.code ?? "IMPORT_ROW_FAILED";
726
- const message = typeof err?.message === "string" ? err.message.slice(0, 300) : "Row failed";
727
- results.push({ row: rowNo, ok: false, action: "failed", error: message, code });
779
+ results[i] = toFailedResult(rowNo, err);
728
780
  }
729
781
  const processed = i + 1;
730
- if (onProgress && (processed % progressEvery === 0 || processed === rows.length)) {
731
- await onProgress(snapshot(processed));
782
+ if (processed % progressEvery === 0 || processed === rows.length) {
783
+ await flushPendingCreates();
784
+ if (onProgress) await onProgress(snapshot(processed));
732
785
  }
733
786
  if (shouldCancel && processed < rows.length && processed % progressEvery === 0) {
734
787
  if (await shouldCancel()) {
@@ -737,10 +790,12 @@ function runImport(opts) {
737
790
  }
738
791
  }
739
792
  }
793
+ await flushPendingCreates();
794
+ const compacted = results.filter((r) => r !== void 0);
740
795
  return {
741
- ...snapshot(results.length),
796
+ ...snapshot(compacted.length),
742
797
  ok: okCount,
743
- results,
798
+ results: compacted,
744
799
  cancelled,
745
800
  ...collectUndo ? { undoLog } : {}
746
801
  };
@@ -1561,7 +1616,7 @@ var RestServer = class {
1561
1616
  */
1562
1617
  enforceAuth(req, res, context) {
1563
1618
  const gate = context?.authGate;
1564
- if (gate && req?.method !== "OPTIONS" && !(0, import_core.isAuthGateAllowlisted)(req?.path)) {
1619
+ if (gate && req?.method !== "OPTIONS" && !(0, import_core2.isAuthGateAllowlisted)(req?.path)) {
1565
1620
  res.status(403).json({ error: { code: gate.code, message: gate.message } });
1566
1621
  return true;
1567
1622
  }
@@ -1717,10 +1772,10 @@ var RestServer = class {
1717
1772
  return void 0;
1718
1773
  }
1719
1774
  };
1720
- const authz = await (0, import_core.resolveAuthzContext)({ ql, headers, getSession });
1775
+ const authz = await (0, import_core2.resolveAuthzContext)({ ql, headers, getSession });
1721
1776
  if (!authz.userId) return void 0;
1722
1777
  const settings = this.settingsServiceProvider ? await this.settingsServiceProvider(environmentId).catch(() => void 0) : void 0;
1723
- const localization = await (0, import_core.resolveLocalizationContext)({
1778
+ const localization = await (0, import_core2.resolveLocalizationContext)({
1724
1779
  ql,
1725
1780
  settings,
1726
1781
  tenantId: authz.tenantId,