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