@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 +82 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +78 -23
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/rest-server.ts
|
|
2
2
|
import { resolveAuthzContext, resolveLocalizationContext, isAuthGateAllowlisted } from "@objectstack/core";
|
|
3
|
+
import { isMcpServerEnabled } from "@objectstack/types";
|
|
3
4
|
|
|
4
5
|
// src/route-manager.ts
|
|
5
6
|
var RouteManager = class {
|
|
@@ -519,6 +520,17 @@ async function coerceRow(rawRow, metaMap, ctx) {
|
|
|
519
520
|
}
|
|
520
521
|
|
|
521
522
|
// src/import-runner.ts
|
|
523
|
+
import { bulkWrite, withTransientRetry } from "@objectstack/core";
|
|
524
|
+
function extractRecordId(rec) {
|
|
525
|
+
const id = rec?.id ?? rec?.record?.id;
|
|
526
|
+
return id != null ? String(id) : void 0;
|
|
527
|
+
}
|
|
528
|
+
function toFailedResult(rowNo, err) {
|
|
529
|
+
const code = err?.code ?? "IMPORT_ROW_FAILED";
|
|
530
|
+
const message = typeof err?.message === "string" ? err.message.slice(0, 300) : "Row failed";
|
|
531
|
+
return { row: rowNo, ok: false, action: "failed", error: message, code };
|
|
532
|
+
}
|
|
533
|
+
var MAX_CREATE_BATCH_SIZE = 200;
|
|
522
534
|
function runImport(opts) {
|
|
523
535
|
const {
|
|
524
536
|
p,
|
|
@@ -606,7 +618,7 @@ function runImport(opts) {
|
|
|
606
618
|
return recs[0];
|
|
607
619
|
};
|
|
608
620
|
const writeCtx = { ...context ?? {}, skipAutomations: !runAutomations };
|
|
609
|
-
const results =
|
|
621
|
+
const results = new Array(rows.length);
|
|
610
622
|
let okCount = 0, errCount = 0, created = 0, updated = 0, skipped = 0;
|
|
611
623
|
let cancelled = false;
|
|
612
624
|
const snapshot = (processed) => ({
|
|
@@ -617,6 +629,47 @@ function runImport(opts) {
|
|
|
617
629
|
skipped,
|
|
618
630
|
errors: errCount
|
|
619
631
|
});
|
|
632
|
+
const canBulkCreate = typeof p.createManyData === "function";
|
|
633
|
+
const pendingCreates = [];
|
|
634
|
+
const flushPendingCreates = async () => {
|
|
635
|
+
if (pendingCreates.length === 0) return;
|
|
636
|
+
const batch = pendingCreates.splice(0, pendingCreates.length);
|
|
637
|
+
const writeResults = await bulkWrite(
|
|
638
|
+
batch.map((b) => b.data),
|
|
639
|
+
{
|
|
640
|
+
// Flush cadence follows progressEvery, but the write batch itself is
|
|
641
|
+
// capped independently — a caller-supplied progressEvery far above
|
|
642
|
+
// the issue's suggested 100-500 rows/batch must not translate into
|
|
643
|
+
// one oversized multi-row INSERT statement.
|
|
644
|
+
batchSize: Math.min(progressEvery, MAX_CREATE_BATCH_SIZE),
|
|
645
|
+
writeBatch: (chunk) => p.createManyData({
|
|
646
|
+
object: objectName,
|
|
647
|
+
records: chunk,
|
|
648
|
+
context: writeCtx,
|
|
649
|
+
...environmentId ? { environmentId } : {}
|
|
650
|
+
}).then((r) => r.records),
|
|
651
|
+
writeOne: (row) => p.createData({
|
|
652
|
+
object: objectName,
|
|
653
|
+
data: row,
|
|
654
|
+
context: writeCtx,
|
|
655
|
+
...environmentId ? { environmentId } : {}
|
|
656
|
+
})
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
for (const res of writeResults) {
|
|
660
|
+
const { index, rowNo } = batch[res.index];
|
|
661
|
+
if (res.ok) {
|
|
662
|
+
const id = extractRecordId(res.record);
|
|
663
|
+
okCount++;
|
|
664
|
+
created++;
|
|
665
|
+
if (collectUndo && id != null) undoLog.created.push(id);
|
|
666
|
+
results[index] = { row: rowNo, ok: true, action: "created", id };
|
|
667
|
+
} else {
|
|
668
|
+
errCount++;
|
|
669
|
+
results[index] = toFailedResult(rowNo, res.error);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
620
673
|
return (async () => {
|
|
621
674
|
for (let i = 0; i < rows.length; i++) {
|
|
622
675
|
const rowNo = i + 1;
|
|
@@ -630,7 +683,7 @@ function runImport(opts) {
|
|
|
630
683
|
if (errors.length > 0) {
|
|
631
684
|
const first2 = errors[0];
|
|
632
685
|
errCount++;
|
|
633
|
-
results
|
|
686
|
+
results[i] = { row: rowNo, ok: false, action: "failed", field: first2.field, code: first2.code, error: first2.message };
|
|
634
687
|
} else {
|
|
635
688
|
let existing = "none";
|
|
636
689
|
let handled = false;
|
|
@@ -638,11 +691,11 @@ function runImport(opts) {
|
|
|
638
691
|
existing = await findExisting(data);
|
|
639
692
|
if (existing === "ambiguous") {
|
|
640
693
|
errCount++;
|
|
641
|
-
results
|
|
694
|
+
results[i] = { row: rowNo, ok: false, action: "failed", code: "AMBIGUOUS_MATCH", error: `matchFields matched more than one ${objectName} record` };
|
|
642
695
|
handled = true;
|
|
643
696
|
} else if (existing === "blank" && (skipBlankMatchKey || writeMode === "update")) {
|
|
644
697
|
skipped++;
|
|
645
|
-
results
|
|
698
|
+
results[i] = { row: rowNo, ok: true, action: "skipped", code: "BLANK_MATCH_KEY" };
|
|
646
699
|
handled = true;
|
|
647
700
|
}
|
|
648
701
|
}
|
|
@@ -651,45 +704,46 @@ function runImport(opts) {
|
|
|
651
704
|
const willCreate = !willUpdate && (writeMode === "insert" || writeMode === "upsert");
|
|
652
705
|
if (!willUpdate && !willCreate) {
|
|
653
706
|
skipped++;
|
|
654
|
-
results
|
|
707
|
+
results[i] = { row: rowNo, ok: true, action: "skipped", code: "NO_MATCH" };
|
|
655
708
|
} else if (dryRun) {
|
|
656
709
|
okCount++;
|
|
657
710
|
if (willUpdate) {
|
|
658
711
|
updated++;
|
|
659
|
-
results
|
|
712
|
+
results[i] = { row: rowNo, ok: true, action: "updated", id: String(existing.id ?? "") || void 0 };
|
|
660
713
|
} else {
|
|
661
714
|
created++;
|
|
662
|
-
results
|
|
715
|
+
results[i] = { row: rowNo, ok: true, action: "created" };
|
|
663
716
|
}
|
|
664
717
|
} else if (willUpdate) {
|
|
665
718
|
const target = existing;
|
|
666
|
-
const res2 = await p.updateData({ object: objectName, id: target.id, data, context: writeCtx, ...environmentId ? { environmentId } : {} });
|
|
667
|
-
const id = res2
|
|
719
|
+
const res2 = await withTransientRetry(() => p.updateData({ object: objectName, id: target.id, data, context: writeCtx, ...environmentId ? { environmentId } : {} }));
|
|
720
|
+
const id = extractRecordId(res2) ?? String(target.id);
|
|
668
721
|
okCount++;
|
|
669
722
|
updated++;
|
|
670
723
|
if (collectUndo && target.id != null) {
|
|
671
724
|
undoLog.updated.push({ id: String(target.id), before: captureBefore(target, data) });
|
|
672
725
|
}
|
|
673
|
-
results
|
|
726
|
+
results[i] = { row: rowNo, ok: true, action: "updated", id };
|
|
727
|
+
} else if (canBulkCreate) {
|
|
728
|
+
pendingCreates.push({ index: i, rowNo, data });
|
|
674
729
|
} else {
|
|
675
730
|
const res2 = await p.createData({ object: objectName, data, context: writeCtx, ...environmentId ? { environmentId } : {} });
|
|
676
|
-
const id = res2
|
|
731
|
+
const id = extractRecordId(res2);
|
|
677
732
|
okCount++;
|
|
678
733
|
created++;
|
|
679
|
-
if (collectUndo && id != null) undoLog.created.push(
|
|
680
|
-
results
|
|
734
|
+
if (collectUndo && id != null) undoLog.created.push(id);
|
|
735
|
+
results[i] = { row: rowNo, ok: true, action: "created", id };
|
|
681
736
|
}
|
|
682
737
|
}
|
|
683
738
|
}
|
|
684
739
|
} catch (err) {
|
|
685
740
|
errCount++;
|
|
686
|
-
|
|
687
|
-
const message = typeof err?.message === "string" ? err.message.slice(0, 300) : "Row failed";
|
|
688
|
-
results.push({ row: rowNo, ok: false, action: "failed", error: message, code });
|
|
741
|
+
results[i] = toFailedResult(rowNo, err);
|
|
689
742
|
}
|
|
690
743
|
const processed = i + 1;
|
|
691
|
-
if (
|
|
692
|
-
await
|
|
744
|
+
if (processed % progressEvery === 0 || processed === rows.length) {
|
|
745
|
+
await flushPendingCreates();
|
|
746
|
+
if (onProgress) await onProgress(snapshot(processed));
|
|
693
747
|
}
|
|
694
748
|
if (shouldCancel && processed < rows.length && processed % progressEvery === 0) {
|
|
695
749
|
if (await shouldCancel()) {
|
|
@@ -698,10 +752,12 @@ function runImport(opts) {
|
|
|
698
752
|
}
|
|
699
753
|
}
|
|
700
754
|
}
|
|
755
|
+
await flushPendingCreates();
|
|
756
|
+
const compacted = results.filter((r) => r !== void 0);
|
|
701
757
|
return {
|
|
702
|
-
...snapshot(
|
|
758
|
+
...snapshot(compacted.length),
|
|
703
759
|
ok: okCount,
|
|
704
|
-
results,
|
|
760
|
+
results: compacted,
|
|
705
761
|
cancelled,
|
|
706
762
|
...collectUndo ? { undoLog } : {}
|
|
707
763
|
};
|
|
@@ -1698,7 +1754,7 @@ var RestServer = class {
|
|
|
1698
1754
|
userId: authz.userId,
|
|
1699
1755
|
tenantId: authz.tenantId,
|
|
1700
1756
|
email: authz.email,
|
|
1701
|
-
|
|
1757
|
+
positions: authz.positions,
|
|
1702
1758
|
permissions: authz.permissions,
|
|
1703
1759
|
systemPermissions: authz.systemPermissions,
|
|
1704
1760
|
...authz.tabPermissions ? { tabPermissions: authz.tabPermissions } : {},
|
|
@@ -2156,8 +2212,7 @@ var RestServer = class {
|
|
|
2156
2212
|
if (this.config.api.enableUi) {
|
|
2157
2213
|
discovery.routes.ui = `${realBase}/ui`;
|
|
2158
2214
|
}
|
|
2159
|
-
|
|
2160
|
-
if (mcpEnabled) {
|
|
2215
|
+
if (isMcpServerEnabled()) {
|
|
2161
2216
|
const unscopedBase = isScoped ? basePath.replace(/\/(environments|projects)\/:environmentId$/, "") : basePath;
|
|
2162
2217
|
discovery.routes.mcp = `${unscopedBase}/mcp`;
|
|
2163
2218
|
} else {
|