@agent-native/core 0.90.4 → 0.90.7
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/README.md +34 -7
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/toolkit-agent-ux.mdx +1 -1
- package/corpus/core/docs/content/toolkit-app-adapters.mdx +1 -1
- package/corpus/core/docs/content/toolkit-collaboration-ui.mdx +1 -1
- package/corpus/core/docs/content/toolkit-collaboration.mdx +1 -1
- package/corpus/core/docs/content/toolkit-command-navigation.mdx +2 -2
- package/corpus/core/docs/content/toolkit-comments-review.mdx +1 -1
- package/corpus/core/docs/content/toolkit-history.mdx +1 -1
- package/corpus/core/docs/content/toolkit-observability.mdx +1 -1
- package/corpus/core/docs/content/toolkit-org-team.mdx +1 -1
- package/corpus/core/docs/content/toolkit-resources.mdx +1 -1
- package/corpus/core/docs/content/toolkit-settings.mdx +1 -1
- package/corpus/core/docs/content/toolkit-setup-connections.mdx +1 -1
- package/corpus/core/docs/content/toolkit-sharing-ui.mdx +1 -1
- package/corpus/core/docs/content/toolkit-sharing.mdx +1 -1
- package/corpus/core/docs/content/toolkit-shell-hooks.mdx +1 -1
- package/corpus/core/docs/content/toolkit-ui.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +18 -18
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
- package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
- package/corpus/core/src/agent/production-agent.ts +22 -11
- package/corpus/core/src/agent/run-manager.ts +17 -6
- package/corpus/core/src/agent/run-store.ts +16 -11
- package/corpus/core/src/cli/design-connect.ts +16 -2
- package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
- package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
- package/corpus/core/src/client/use-db-sync.ts +99 -1
- package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
- package/corpus/core/src/collab/client.ts +49 -4
- package/corpus/core/src/deploy/build.ts +103 -0
- package/corpus/core/src/server/core-routes-plugin.ts +137 -8
- package/corpus/templates/clips/changelog/2026-07-07-faster-local-whisper-dictation-start.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +311 -31
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +2 -0
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
- package/corpus/templates/content/actions/_database-utils.ts +2 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
- package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
- package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
- package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
- package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/content/app/i18n-data.ts +31 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
- package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
- package/corpus/templates/content/parity/matrix.md +24 -24
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/shared/api.ts +59 -4
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +9 -0
- package/corpus/templates/design/actions/connect-localhost.ts +26 -3
- package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
- package/corpus/templates/design/actions/read-source-file.ts +28 -0
- package/corpus/templates/design/actions/update-file.ts +76 -7
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +207 -11
- package/corpus/templates/design/app/components/design/EditPanel.tsx +4 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +387 -25
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1110 -88
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +368 -16
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +24 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +27 -6
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +18 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +5 -4
- package/corpus/templates/design/app/components/design/multi-screen/handle-hit-zones.ts +124 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/i18n-data.ts +19 -0
- package/corpus/templates/design/changelog/2026-07-07-dragging-a-free-floating-element-into-a-layout-column-or-row.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-dragging-a-repeated-list-item-like-a-to-do-card-generated-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-dragging-an-element-onto-a-button-or-chip-s-middle-no-longer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-elements-dropped-into-another-board-shape-now-stay-exactly-w.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-drag-reordering-elements-within-a-screen-no-longer-sil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-an-element-from-a-screen-onto-the-canvas-now-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-elements-between-screens-into-ai-generated-co.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-moving-elements-between-screens-no-longer-bakes-intern.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-the-cross-screen-drop-indicator-now-points-at-the-corr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-the-zoom-menu-no-longer-leaves-the-canvas-unresponsive.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-text-drawn-on-the-canvas-is-now-readable-white-instead-of-in.md +6 -0
- package/corpus/templates/design/server/source-workspace.ts +27 -3
- package/corpus/templates/design/shared/board-file.ts +359 -0
- package/corpus/templates/design/shared/breakpoint-media.ts +21 -3
- package/corpus/templates/design/shared/code-layer.ts +145 -10
- package/dist/agent/durable-background.d.ts +12 -12
- package/dist/agent/durable-background.js +18 -18
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +8 -2
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -1
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -11
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +7 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +6 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +15 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +12 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/KeepTabOpenNotice.d.ts +9 -10
- package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
- package/dist/client/KeepTabOpenNotice.js +8 -4
- package/dist/client/KeepTabOpenNotice.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +14 -13
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +25 -17
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +18 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +88 -1
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +6 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +43 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +1 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +57 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +10 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +103 -9
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-app-adapters.mdx +1 -1
- package/docs/content/toolkit-collaboration-ui.mdx +1 -1
- package/docs/content/toolkit-collaboration.mdx +1 -1
- package/docs/content/toolkit-command-navigation.mdx +2 -2
- package/docs/content/toolkit-comments-review.mdx +1 -1
- package/docs/content/toolkit-history.mdx +1 -1
- package/docs/content/toolkit-observability.mdx +1 -1
- package/docs/content/toolkit-org-team.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +1 -1
- package/docs/content/toolkit-settings.mdx +1 -1
- package/docs/content/toolkit-setup-connections.mdx +1 -1
- package/docs/content/toolkit-sharing-ui.mdx +1 -1
- package/docs/content/toolkit-sharing.mdx +1 -1
- package/docs/content/toolkit-shell-hooks.mdx +1 -1
- package/docs/content/toolkit-ui.mdx +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import type {
|
|
8
|
+
ContentDatabaseSource,
|
|
9
|
+
ContentDatabaseSourceChangeSet,
|
|
10
|
+
ContentDatabaseSourceFieldChange,
|
|
11
|
+
ContentDatabaseSourceFieldMapping,
|
|
12
|
+
DocumentPropertyValue,
|
|
13
|
+
StageBuilderSourceBulkUpdateRequest,
|
|
14
|
+
StageBuilderSourceBulkUpdateResponse,
|
|
15
|
+
StageBuilderSourceBulkUpdateRowResult,
|
|
16
|
+
} from "../shared/api.js";
|
|
17
|
+
import {
|
|
18
|
+
isBlocksPropertyType,
|
|
19
|
+
isComputedPropertyType,
|
|
20
|
+
normalizePropertyValue,
|
|
21
|
+
type DocumentPropertyType,
|
|
22
|
+
} from "../shared/properties.js";
|
|
23
|
+
import { BUILDER_CMS_FIXTURE_ROW_PROVENANCE } from "./_builder-cms-source-adapter.js";
|
|
24
|
+
import {
|
|
25
|
+
DATABASE_ROW_BATCH_LIMIT,
|
|
26
|
+
resolveDatabaseRowsForBatch,
|
|
27
|
+
} from "./_database-row-batch.js";
|
|
28
|
+
import {
|
|
29
|
+
getContentDatabaseSourceSnapshotForWrite,
|
|
30
|
+
resolveDatabaseForSourceMutation,
|
|
31
|
+
} from "./_database-source-utils.js";
|
|
32
|
+
import { nanoid, normalizedValueJson } from "./_property-utils.js";
|
|
33
|
+
import { buildBuilderSourceReviewPayload } from "./prepare-builder-source-review.js";
|
|
34
|
+
|
|
35
|
+
const supportedBulkPropertyTypes = new Set<DocumentPropertyType>([
|
|
36
|
+
"text",
|
|
37
|
+
"number",
|
|
38
|
+
"checkbox",
|
|
39
|
+
"url",
|
|
40
|
+
"email",
|
|
41
|
+
"phone",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
function parseSourceValues(
|
|
45
|
+
value: string | Record<string, DocumentPropertyValue> | null | undefined,
|
|
46
|
+
) {
|
|
47
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
if (!value) return {};
|
|
51
|
+
if (typeof value !== "string") return {};
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(value) as unknown;
|
|
54
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
55
|
+
? (parsed as Record<string, DocumentPropertyValue>)
|
|
56
|
+
: {};
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stableValueString(value: unknown): string {
|
|
63
|
+
if (value === null || value === undefined) return "null";
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return `[${value.map(stableValueString).join(",")}]`;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === "object") {
|
|
68
|
+
const record = value as Record<string, unknown>;
|
|
69
|
+
return `{${Object.keys(record)
|
|
70
|
+
.sort()
|
|
71
|
+
.map((key) => `${JSON.stringify(key)}:${stableValueString(record[key])}`)
|
|
72
|
+
.join(",")}}`;
|
|
73
|
+
}
|
|
74
|
+
return JSON.stringify(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sameSourceFieldValue(a: unknown, b: unknown): boolean {
|
|
78
|
+
const normalize = (value: unknown) => {
|
|
79
|
+
if (value === null || value === undefined) return "";
|
|
80
|
+
if (typeof value === "string") return value.trim();
|
|
81
|
+
return stableValueString(value);
|
|
82
|
+
};
|
|
83
|
+
return normalize(a) === normalize(b);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function openChangeSetForDocument(
|
|
87
|
+
source: ContentDatabaseSource,
|
|
88
|
+
documentId: string,
|
|
89
|
+
) {
|
|
90
|
+
return source.changeSets.find(
|
|
91
|
+
(changeSet) =>
|
|
92
|
+
changeSet.documentId === documentId &&
|
|
93
|
+
changeSet.direction === "outbound" &&
|
|
94
|
+
(changeSet.state === "pending_push" ||
|
|
95
|
+
changeSet.state === "staged_revision" ||
|
|
96
|
+
changeSet.state === "approved"),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolveSourceField(args: {
|
|
101
|
+
source: ContentDatabaseSource;
|
|
102
|
+
field: StageBuilderSourceBulkUpdateRequest["field"];
|
|
103
|
+
}) {
|
|
104
|
+
const candidates = args.source.fields.filter(
|
|
105
|
+
(field) =>
|
|
106
|
+
field.mappingType === "property" &&
|
|
107
|
+
(args.field.propertyId
|
|
108
|
+
? field.propertyId === args.field.propertyId
|
|
109
|
+
: true) &&
|
|
110
|
+
(args.field.localFieldKey
|
|
111
|
+
? field.localFieldKey === args.field.localFieldKey
|
|
112
|
+
: true) &&
|
|
113
|
+
(args.field.sourceFieldKey
|
|
114
|
+
? field.sourceFieldKey === args.field.sourceFieldKey
|
|
115
|
+
: true),
|
|
116
|
+
);
|
|
117
|
+
if (candidates.length > 1) {
|
|
118
|
+
throw new Error("Mapped Builder source field is ambiguous.");
|
|
119
|
+
}
|
|
120
|
+
return candidates[0] ?? null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function fieldBlocker(field: ContentDatabaseSourceFieldMapping) {
|
|
124
|
+
if (field.readOnly) return "The selected Builder field is read-only.";
|
|
125
|
+
if (field.writeOwner !== "source") {
|
|
126
|
+
return "Only source-owned Builder fields can be staged for Builder writeback.";
|
|
127
|
+
}
|
|
128
|
+
if (!field.propertyId) {
|
|
129
|
+
return "Bulk Builder updates currently require a mapped database property.";
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function sourceBlocker(source: ContentDatabaseSource) {
|
|
135
|
+
if (source.sourceType !== "builder-cms") {
|
|
136
|
+
return "Attach a Builder CMS source before staging a bulk update.";
|
|
137
|
+
}
|
|
138
|
+
if (!source.capabilities.canCreateChangeSets) {
|
|
139
|
+
return "This Builder source cannot create reviewable change sets.";
|
|
140
|
+
}
|
|
141
|
+
if (!source.capabilities.canWriteFields) {
|
|
142
|
+
return "This Builder source does not allow field writeback.";
|
|
143
|
+
}
|
|
144
|
+
if (source.freshness !== "fresh") {
|
|
145
|
+
return "Refresh the Builder source before staging bulk updates.";
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function reviewableRowBlocker(
|
|
151
|
+
source: ContentDatabaseSource,
|
|
152
|
+
row: ContentDatabaseSource["rows"][number],
|
|
153
|
+
) {
|
|
154
|
+
const skipsFixtureRows =
|
|
155
|
+
source.metadata.liveReadConfigured === true ||
|
|
156
|
+
source.capabilities.liveWritesEnabled === true;
|
|
157
|
+
if (
|
|
158
|
+
skipsFixtureRows &&
|
|
159
|
+
row.provenance === BUILDER_CMS_FIXTURE_ROW_PROVENANCE
|
|
160
|
+
) {
|
|
161
|
+
return "Refresh this Builder row from the live source before staging a bulk update.";
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function rowTitle(row: { document: { title?: string | null } }) {
|
|
167
|
+
return row.document.title?.trim() || "Untitled";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function summarizeRows(rows: StageBuilderSourceBulkUpdateRowResult[]) {
|
|
171
|
+
return {
|
|
172
|
+
total: rows.length,
|
|
173
|
+
staged: rows.filter((row) => row.status === "staged").length,
|
|
174
|
+
unchanged: rows.filter((row) => row.status === "unchanged").length,
|
|
175
|
+
blocked: rows.filter((row) => row.status === "blocked").length,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function previewChangeSet(args: {
|
|
180
|
+
source: ContentDatabaseSource;
|
|
181
|
+
row: StageBuilderSourceBulkUpdateRowResult;
|
|
182
|
+
fieldChange: ContentDatabaseSourceFieldChange;
|
|
183
|
+
now: string;
|
|
184
|
+
}): ContentDatabaseSourceChangeSet {
|
|
185
|
+
return {
|
|
186
|
+
id: args.row.changeSetId ?? `bulk-preview-${args.row.itemId}`,
|
|
187
|
+
databaseItemId: args.row.itemId,
|
|
188
|
+
documentId: args.row.documentId,
|
|
189
|
+
kind: "field_update",
|
|
190
|
+
direction: "outbound",
|
|
191
|
+
state: "pending_push",
|
|
192
|
+
pushMode: args.source.metadata.pushMode ?? "autosave",
|
|
193
|
+
localOnly: true,
|
|
194
|
+
summary: `Pending local Builder CMS bulk update for "${args.row.title}".`,
|
|
195
|
+
fieldChanges: [args.fieldChange],
|
|
196
|
+
bodyChange: null,
|
|
197
|
+
riskLevel: "low",
|
|
198
|
+
riskReasons: ["bulk field update"],
|
|
199
|
+
conflictState: "none",
|
|
200
|
+
reviewEvents: [],
|
|
201
|
+
executions: [],
|
|
202
|
+
createdAt: args.now,
|
|
203
|
+
updatedAt: args.now,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function mergedFieldChanges(
|
|
208
|
+
current: ContentDatabaseSourceFieldChange[],
|
|
209
|
+
next: ContentDatabaseSourceFieldChange,
|
|
210
|
+
) {
|
|
211
|
+
const withoutExisting = current.filter(
|
|
212
|
+
(change) =>
|
|
213
|
+
change.sourceFieldKey !== next.sourceFieldKey &&
|
|
214
|
+
change.localFieldKey !== next.localFieldKey,
|
|
215
|
+
);
|
|
216
|
+
return [...withoutExisting, next];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function propertyDefinition(args: {
|
|
220
|
+
databaseId: string;
|
|
221
|
+
ownerEmail: string;
|
|
222
|
+
propertyId: string;
|
|
223
|
+
}) {
|
|
224
|
+
const [definition] = await getDb()
|
|
225
|
+
.select()
|
|
226
|
+
.from(schema.documentPropertyDefinitions)
|
|
227
|
+
.where(
|
|
228
|
+
and(
|
|
229
|
+
eq(schema.documentPropertyDefinitions.id, args.propertyId),
|
|
230
|
+
eq(schema.documentPropertyDefinitions.databaseId, args.databaseId),
|
|
231
|
+
eq(schema.documentPropertyDefinitions.ownerEmail, args.ownerEmail),
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
return definition ?? null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function upsertPropertyValues(args: {
|
|
238
|
+
ownerEmail: string;
|
|
239
|
+
documentIds: string[];
|
|
240
|
+
propertyId: string;
|
|
241
|
+
valueJson: string;
|
|
242
|
+
now: string;
|
|
243
|
+
}) {
|
|
244
|
+
const db = getDb();
|
|
245
|
+
const existing = await db
|
|
246
|
+
.select({
|
|
247
|
+
id: schema.documentPropertyValues.id,
|
|
248
|
+
documentId: schema.documentPropertyValues.documentId,
|
|
249
|
+
})
|
|
250
|
+
.from(schema.documentPropertyValues)
|
|
251
|
+
.where(
|
|
252
|
+
and(
|
|
253
|
+
eq(schema.documentPropertyValues.propertyId, args.propertyId),
|
|
254
|
+
inArray(schema.documentPropertyValues.documentId, args.documentIds),
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
const existingByDocumentId = new Map(
|
|
258
|
+
existing.map((row) => [row.documentId, row.id]),
|
|
259
|
+
);
|
|
260
|
+
const existingIds = existing.map((row) => row.id);
|
|
261
|
+
if (existingIds.length > 0) {
|
|
262
|
+
await db
|
|
263
|
+
.update(schema.documentPropertyValues)
|
|
264
|
+
.set({ valueJson: args.valueJson, updatedAt: args.now })
|
|
265
|
+
.where(inArray(schema.documentPropertyValues.id, existingIds));
|
|
266
|
+
}
|
|
267
|
+
const inserts = args.documentIds
|
|
268
|
+
.filter((documentId) => !existingByDocumentId.has(documentId))
|
|
269
|
+
.map((documentId) => ({
|
|
270
|
+
id: nanoid(),
|
|
271
|
+
ownerEmail: args.ownerEmail,
|
|
272
|
+
documentId,
|
|
273
|
+
propertyId: args.propertyId,
|
|
274
|
+
valueJson: args.valueJson,
|
|
275
|
+
createdAt: args.now,
|
|
276
|
+
updatedAt: args.now,
|
|
277
|
+
}));
|
|
278
|
+
if (inserts.length > 0) {
|
|
279
|
+
await db.insert(schema.documentPropertyValues).values(inserts);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function updateOpenChangeSetsForStagedRows(args: {
|
|
284
|
+
source: ContentDatabaseSource;
|
|
285
|
+
rows: StageBuilderSourceBulkUpdateRowResult[];
|
|
286
|
+
now: string;
|
|
287
|
+
}) {
|
|
288
|
+
const db = getDb();
|
|
289
|
+
for (const row of args.rows) {
|
|
290
|
+
if (row.status !== "staged" || !row.fieldChange) continue;
|
|
291
|
+
const openChangeSet = openChangeSetForDocument(args.source, row.documentId);
|
|
292
|
+
if (!openChangeSet) continue;
|
|
293
|
+
const fieldChanges = mergedFieldChanges(
|
|
294
|
+
openChangeSet.fieldChanges,
|
|
295
|
+
row.fieldChange,
|
|
296
|
+
);
|
|
297
|
+
await db
|
|
298
|
+
.update(schema.contentDatabaseSourceChangeSets)
|
|
299
|
+
.set({
|
|
300
|
+
state: "pending_push",
|
|
301
|
+
summary: `Pending local Builder CMS changes for "${row.title}".`,
|
|
302
|
+
fieldChangesJson: JSON.stringify(fieldChanges),
|
|
303
|
+
updatedAt: args.now,
|
|
304
|
+
})
|
|
305
|
+
.where(eq(schema.contentDatabaseSourceChangeSets.id, openChangeSet.id));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function stageBuilderSourceBulkUpdateWithDeps(
|
|
310
|
+
args: StageBuilderSourceBulkUpdateRequest,
|
|
311
|
+
): Promise<StageBuilderSourceBulkUpdateResponse> {
|
|
312
|
+
const database = await resolveDatabaseForSourceMutation(args);
|
|
313
|
+
if (!database) throw new Error("Database not found.");
|
|
314
|
+
await assertAccess("document", database.documentId, "editor");
|
|
315
|
+
|
|
316
|
+
const { rows } = await resolveDatabaseRowsForBatch({
|
|
317
|
+
databaseId: database.id,
|
|
318
|
+
itemIds: args.itemIds,
|
|
319
|
+
documentIds: args.documentIds,
|
|
320
|
+
});
|
|
321
|
+
const source = await getContentDatabaseSourceSnapshotForWrite(
|
|
322
|
+
database,
|
|
323
|
+
args.sourceId,
|
|
324
|
+
);
|
|
325
|
+
if (!source) throw new Error("Attach a source before staging bulk updates.");
|
|
326
|
+
|
|
327
|
+
const sourceLevelBlocker = sourceBlocker(source);
|
|
328
|
+
const field = resolveSourceField({ source, field: args.field });
|
|
329
|
+
if (!field) throw new Error("Mapped Builder source field not found.");
|
|
330
|
+
const fieldLevelBlocker = fieldBlocker(field);
|
|
331
|
+
const definition = field.propertyId
|
|
332
|
+
? await propertyDefinition({
|
|
333
|
+
databaseId: database.id,
|
|
334
|
+
ownerEmail: database.ownerEmail,
|
|
335
|
+
propertyId: field.propertyId,
|
|
336
|
+
})
|
|
337
|
+
: null;
|
|
338
|
+
if (field.propertyId && !definition) {
|
|
339
|
+
throw new Error("Mapped database property not found.");
|
|
340
|
+
}
|
|
341
|
+
const propertyType = definition?.type as DocumentPropertyType | undefined;
|
|
342
|
+
const propertyBlocker =
|
|
343
|
+
propertyType &&
|
|
344
|
+
(isComputedPropertyType(propertyType) ||
|
|
345
|
+
isBlocksPropertyType(propertyType) ||
|
|
346
|
+
!supportedBulkPropertyTypes.has(propertyType))
|
|
347
|
+
? "This property type is not supported for Builder bulk updates yet."
|
|
348
|
+
: null;
|
|
349
|
+
const normalizedValue = propertyType
|
|
350
|
+
? normalizePropertyValue(propertyType, args.field.value)
|
|
351
|
+
: args.field.value;
|
|
352
|
+
const valueJson = propertyType
|
|
353
|
+
? normalizedValueJson(propertyType, normalizedValue)
|
|
354
|
+
: null;
|
|
355
|
+
const now = new Date().toISOString();
|
|
356
|
+
const sourceRowsByDocumentId = new Map(
|
|
357
|
+
source.rows.map((row) => [row.documentId, row]),
|
|
358
|
+
);
|
|
359
|
+
const results: StageBuilderSourceBulkUpdateRowResult[] = [];
|
|
360
|
+
const previewChangeSets: ContentDatabaseSourceChangeSet[] = [];
|
|
361
|
+
|
|
362
|
+
for (const row of rows) {
|
|
363
|
+
const sourceRow = sourceRowsByDocumentId.get(row.document.id);
|
|
364
|
+
const title = rowTitle(row);
|
|
365
|
+
const baseResult = {
|
|
366
|
+
itemId: row.item.id,
|
|
367
|
+
documentId: row.document.id,
|
|
368
|
+
title,
|
|
369
|
+
};
|
|
370
|
+
const blocker =
|
|
371
|
+
sourceLevelBlocker ??
|
|
372
|
+
fieldLevelBlocker ??
|
|
373
|
+
propertyBlocker ??
|
|
374
|
+
(!sourceRow
|
|
375
|
+
? "Selected row is not backed by the target Builder source."
|
|
376
|
+
: null) ??
|
|
377
|
+
(sourceRow?.freshness !== "fresh" || sourceRow?.syncState === "error"
|
|
378
|
+
? "Refresh this Builder row before staging a bulk update."
|
|
379
|
+
: null) ??
|
|
380
|
+
(sourceRow ? reviewableRowBlocker(source, sourceRow) : null);
|
|
381
|
+
if (blocker || !sourceRow) {
|
|
382
|
+
results.push({
|
|
383
|
+
...baseResult,
|
|
384
|
+
status: "blocked",
|
|
385
|
+
message:
|
|
386
|
+
blocker ?? "Selected row is not backed by the target Builder source.",
|
|
387
|
+
});
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const sourceValues = parseSourceValues(sourceRow.sourceValues);
|
|
392
|
+
const currentValue = sourceValues[field.sourceFieldKey] ?? null;
|
|
393
|
+
if (sameSourceFieldValue(currentValue, normalizedValue)) {
|
|
394
|
+
results.push({
|
|
395
|
+
...baseResult,
|
|
396
|
+
status: "unchanged",
|
|
397
|
+
message: "The selected Builder field already has this value.",
|
|
398
|
+
});
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const fieldChange: ContentDatabaseSourceFieldChange = {
|
|
403
|
+
propertyId: field.propertyId,
|
|
404
|
+
propertyName: field.propertyName,
|
|
405
|
+
localFieldKey: field.localFieldKey,
|
|
406
|
+
sourceFieldKey: field.sourceFieldKey,
|
|
407
|
+
currentValue,
|
|
408
|
+
proposedValue: normalizedValue,
|
|
409
|
+
};
|
|
410
|
+
const changeSetId = `bulk-preview-${row.item.id}`;
|
|
411
|
+
const result = {
|
|
412
|
+
...baseResult,
|
|
413
|
+
status: "staged" as const,
|
|
414
|
+
changeSetId,
|
|
415
|
+
fieldChange,
|
|
416
|
+
};
|
|
417
|
+
results.push(result);
|
|
418
|
+
previewChangeSets.push(
|
|
419
|
+
previewChangeSet({ source, row: result, fieldChange, now }),
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const summary = summarizeRows(results);
|
|
424
|
+
const dryRun = args.dryRun !== false;
|
|
425
|
+
if (!dryRun && summary.blocked > 0) {
|
|
426
|
+
const blockedRows = results.map((row) =>
|
|
427
|
+
row.status === "staged"
|
|
428
|
+
? {
|
|
429
|
+
...row,
|
|
430
|
+
status: "blocked" as const,
|
|
431
|
+
message:
|
|
432
|
+
"No rows were staged because at least one selected row is blocked.",
|
|
433
|
+
changeSetId: undefined,
|
|
434
|
+
fieldChange: undefined,
|
|
435
|
+
}
|
|
436
|
+
: row,
|
|
437
|
+
);
|
|
438
|
+
return {
|
|
439
|
+
dryRun,
|
|
440
|
+
databaseId: database.id,
|
|
441
|
+
documentId: database.documentId,
|
|
442
|
+
sourceId: source.id,
|
|
443
|
+
field: {
|
|
444
|
+
propertyId: field.propertyId,
|
|
445
|
+
propertyName: field.propertyName,
|
|
446
|
+
localFieldKey: field.localFieldKey,
|
|
447
|
+
sourceFieldKey: field.sourceFieldKey,
|
|
448
|
+
sourceFieldLabel: field.sourceFieldLabel,
|
|
449
|
+
},
|
|
450
|
+
summary: summarizeRows(blockedRows),
|
|
451
|
+
rows: blockedRows,
|
|
452
|
+
review: null,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (!dryRun && summary.blocked === 0 && summary.staged > 0) {
|
|
456
|
+
if (!field.propertyId || !valueJson) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
"Bulk Builder updates currently require a mapped property.",
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
await upsertPropertyValues({
|
|
462
|
+
ownerEmail: database.ownerEmail,
|
|
463
|
+
documentIds: results
|
|
464
|
+
.filter((row) => row.status === "staged")
|
|
465
|
+
.map((row) => row.documentId),
|
|
466
|
+
propertyId: field.propertyId,
|
|
467
|
+
valueJson,
|
|
468
|
+
now,
|
|
469
|
+
});
|
|
470
|
+
await updateOpenChangeSetsForStagedRows({
|
|
471
|
+
source,
|
|
472
|
+
rows: results,
|
|
473
|
+
now,
|
|
474
|
+
});
|
|
475
|
+
await getDb()
|
|
476
|
+
.update(schema.contentDatabaseSources)
|
|
477
|
+
.set({ updatedAt: now })
|
|
478
|
+
.where(eq(schema.contentDatabaseSources.id, source.id));
|
|
479
|
+
|
|
480
|
+
const updatedSource = await getContentDatabaseSourceSnapshotForWrite(
|
|
481
|
+
database,
|
|
482
|
+
args.sourceId,
|
|
483
|
+
);
|
|
484
|
+
if (!updatedSource) throw new Error("Builder source disappeared.");
|
|
485
|
+
const stagedDocumentIds = new Set(
|
|
486
|
+
results
|
|
487
|
+
.filter((row) => row.status === "staged")
|
|
488
|
+
.map((row) => row.documentId),
|
|
489
|
+
);
|
|
490
|
+
const reviewChangeSets = updatedSource.changeSets.filter(
|
|
491
|
+
(changeSet) =>
|
|
492
|
+
changeSet.documentId &&
|
|
493
|
+
stagedDocumentIds.has(changeSet.documentId) &&
|
|
494
|
+
changeSet.direction === "outbound" &&
|
|
495
|
+
(changeSet.state === "pending_push" ||
|
|
496
|
+
changeSet.state === "staged_revision" ||
|
|
497
|
+
changeSet.state === "approved") &&
|
|
498
|
+
changeSet.fieldChanges.some(
|
|
499
|
+
(change) => change.sourceFieldKey === field.sourceFieldKey,
|
|
500
|
+
),
|
|
501
|
+
);
|
|
502
|
+
const reviewChangeSetByDocumentId = new Map(
|
|
503
|
+
reviewChangeSets.map((changeSet) => [changeSet.documentId, changeSet]),
|
|
504
|
+
);
|
|
505
|
+
const stagedRows = results.map((row) =>
|
|
506
|
+
row.status === "staged"
|
|
507
|
+
? {
|
|
508
|
+
...row,
|
|
509
|
+
changeSetId: reviewChangeSetByDocumentId.get(row.documentId)?.id,
|
|
510
|
+
}
|
|
511
|
+
: row,
|
|
512
|
+
);
|
|
513
|
+
return {
|
|
514
|
+
dryRun,
|
|
515
|
+
databaseId: database.id,
|
|
516
|
+
documentId: database.documentId,
|
|
517
|
+
sourceId: updatedSource.id,
|
|
518
|
+
field: {
|
|
519
|
+
propertyId: field.propertyId,
|
|
520
|
+
propertyName: field.propertyName,
|
|
521
|
+
localFieldKey: field.localFieldKey,
|
|
522
|
+
sourceFieldKey: field.sourceFieldKey,
|
|
523
|
+
sourceFieldLabel: field.sourceFieldLabel,
|
|
524
|
+
},
|
|
525
|
+
summary,
|
|
526
|
+
rows: stagedRows,
|
|
527
|
+
review:
|
|
528
|
+
reviewChangeSets.length > 0
|
|
529
|
+
? buildBuilderSourceReviewPayload({
|
|
530
|
+
source: updatedSource,
|
|
531
|
+
changeSets: reviewChangeSets,
|
|
532
|
+
})
|
|
533
|
+
: null,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
dryRun,
|
|
539
|
+
databaseId: database.id,
|
|
540
|
+
documentId: database.documentId,
|
|
541
|
+
sourceId: source.id,
|
|
542
|
+
field: {
|
|
543
|
+
propertyId: field.propertyId,
|
|
544
|
+
propertyName: field.propertyName,
|
|
545
|
+
localFieldKey: field.localFieldKey,
|
|
546
|
+
sourceFieldKey: field.sourceFieldKey,
|
|
547
|
+
sourceFieldLabel: field.sourceFieldLabel,
|
|
548
|
+
},
|
|
549
|
+
summary,
|
|
550
|
+
rows: results,
|
|
551
|
+
review:
|
|
552
|
+
previewChangeSets.length > 0
|
|
553
|
+
? buildBuilderSourceReviewPayload({
|
|
554
|
+
source,
|
|
555
|
+
changeSets: previewChangeSets,
|
|
556
|
+
})
|
|
557
|
+
: null,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const fieldSchema = z.object({
|
|
562
|
+
propertyId: z.string().optional(),
|
|
563
|
+
localFieldKey: z.string().optional(),
|
|
564
|
+
sourceFieldKey: z.string().optional(),
|
|
565
|
+
value: z
|
|
566
|
+
.union([
|
|
567
|
+
z.string(),
|
|
568
|
+
z.number(),
|
|
569
|
+
z.boolean(),
|
|
570
|
+
z.array(z.string()),
|
|
571
|
+
z
|
|
572
|
+
.object({
|
|
573
|
+
start: z.string(),
|
|
574
|
+
end: z.string().nullable().optional(),
|
|
575
|
+
includeTime: z.boolean().optional(),
|
|
576
|
+
})
|
|
577
|
+
.passthrough(),
|
|
578
|
+
z.null(),
|
|
579
|
+
])
|
|
580
|
+
.describe("Value to set on the mapped Builder-backed field."),
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
export const stageBuilderSourceBulkUpdateSchema = z
|
|
584
|
+
.object({
|
|
585
|
+
databaseId: z.string().optional().describe("Content database ID"),
|
|
586
|
+
documentId: z
|
|
587
|
+
.string()
|
|
588
|
+
.optional()
|
|
589
|
+
.describe("Content database backing document ID"),
|
|
590
|
+
sourceId: z
|
|
591
|
+
.string()
|
|
592
|
+
.optional()
|
|
593
|
+
.describe("Target Builder source ID (defaults to the primary source)"),
|
|
594
|
+
itemIds: z
|
|
595
|
+
.array(z.string())
|
|
596
|
+
.max(DATABASE_ROW_BATCH_LIMIT)
|
|
597
|
+
.optional()
|
|
598
|
+
.describe("Selected database row item IDs to stage in one batch."),
|
|
599
|
+
documentIds: z
|
|
600
|
+
.array(z.string())
|
|
601
|
+
.max(DATABASE_ROW_BATCH_LIMIT)
|
|
602
|
+
.optional()
|
|
603
|
+
.describe("Selected database row document IDs to stage in one batch."),
|
|
604
|
+
field: fieldSchema.describe(
|
|
605
|
+
"One mapped Builder-backed field to update across the selected rows.",
|
|
606
|
+
),
|
|
607
|
+
dryRun: z
|
|
608
|
+
.boolean()
|
|
609
|
+
.optional()
|
|
610
|
+
.describe(
|
|
611
|
+
"Preview only when true or omitted. Set false to stage locally.",
|
|
612
|
+
),
|
|
613
|
+
})
|
|
614
|
+
.superRefine((value, ctx) => {
|
|
615
|
+
if (!value.databaseId && !value.documentId) {
|
|
616
|
+
ctx.addIssue({
|
|
617
|
+
code: z.ZodIssueCode.custom,
|
|
618
|
+
message: "Either databaseId or documentId is required.",
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
const total =
|
|
622
|
+
(value.itemIds?.length ?? 0) + (value.documentIds?.length ?? 0);
|
|
623
|
+
if (total === 0) {
|
|
624
|
+
ctx.addIssue({
|
|
625
|
+
code: z.ZodIssueCode.custom,
|
|
626
|
+
message: "At least one itemId or documentId is required.",
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (total > DATABASE_ROW_BATCH_LIMIT) {
|
|
630
|
+
ctx.addIssue({
|
|
631
|
+
code: z.ZodIssueCode.custom,
|
|
632
|
+
message: `Builder source bulk updates are limited to ${DATABASE_ROW_BATCH_LIMIT} rows.`,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
if (!value.field.propertyId && !value.field.localFieldKey) {
|
|
636
|
+
ctx.addIssue({
|
|
637
|
+
code: z.ZodIssueCode.custom,
|
|
638
|
+
path: ["field"],
|
|
639
|
+
message: "A propertyId or localFieldKey is required.",
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
export { stageBuilderSourceBulkUpdateWithDeps };
|
|
645
|
+
|
|
646
|
+
export default defineAction({
|
|
647
|
+
description:
|
|
648
|
+
"Preview or stage one bounded bulk update across selected source-backed Builder CMS rows. This only stages local reviewable diffs; Builder writeback still goes through the existing review and execution actions.",
|
|
649
|
+
schema: stageBuilderSourceBulkUpdateSchema,
|
|
650
|
+
run: async (
|
|
651
|
+
args: StageBuilderSourceBulkUpdateRequest,
|
|
652
|
+
): Promise<StageBuilderSourceBulkUpdateResponse> => {
|
|
653
|
+
if (
|
|
654
|
+
(args.itemIds?.length ?? 0) + (args.documentIds?.length ?? 0) >
|
|
655
|
+
DATABASE_ROW_BATCH_LIMIT
|
|
656
|
+
) {
|
|
657
|
+
throw new Error(
|
|
658
|
+
`Builder source bulk updates are limited to ${DATABASE_ROW_BATCH_LIMIT} rows.`,
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
return stageBuilderSourceBulkUpdateWithDeps(args);
|
|
662
|
+
},
|
|
663
|
+
});
|