@agent-native/core-corpus 0.216.0 → 0.217.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
- package/corpus/templates/analytics/actions/view-screen.ts +29 -3
- package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
- package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
- package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +67 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
- package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
- package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
- package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
- package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
- package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
- package/corpus/templates/analytics/server/plugins/db.ts +46 -0
- package/corpus/templates/analytics/shared/output-use.ts +205 -0
- package/corpus/templates/analytics/shared/session-events.ts +6 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +2 -4
- package/corpus/templates/calendar/actions/update-event.ts +35 -6
- package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
- package/corpus/templates/content/CHANGELOG.md +8 -0
- package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
- package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
- package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
- package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
- package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
- package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
- package/corpus/templates/content/actions/_database-utils.ts +93 -7
- package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
- package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
- package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
- package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
- package/corpus/templates/content/actions/delete-document.ts +41 -3
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
- package/corpus/templates/content/actions/get-document.ts +2 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
- package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
- package/corpus/templates/content/actions/update-document.ts +139 -20
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
- package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +3 -1
- package/corpus/templates/content/server/db/schema.ts +20 -0
- package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
- package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
- package/corpus/templates/content/server/live-copy/binding.ts +31 -0
- package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
- package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
- package/corpus/templates/content/server/live-copy/holds.ts +59 -0
- package/corpus/templates/content/server/live-copy/persist.ts +656 -0
- package/corpus/templates/content/server/live-copy/switch.ts +136 -0
- package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
- package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
- package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
- package/corpus/templates/content/server/live-copy/write.ts +85 -0
- package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
- package/corpus/templates/content/server/plugins/collab.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +28 -0
- package/corpus/templates/content/shared/api.ts +30 -1
- package/corpus/templates/content/shared/database-item-layout.ts +54 -22
- package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
- package/corpus/templates/content/shared/og-description.ts +258 -36
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
- package/corpus/templates/design/CHANGELOG.md +2 -0
- package/corpus/templates/design/actions/get-getting-started.ts +53 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
- package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
- package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
- package/corpus/templates/design/app/global.css +6 -0
- package/corpus/templates/design/app/i18n/en-US.ts +17 -0
- package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
- package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
- package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
- package/corpus/templates/design/app/pages/Index.tsx +10 -3
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
- package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
- package/corpus/templates/design/server/lib/getting-started.ts +267 -0
- package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
- package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
- package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
- package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
- package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
- package/corpus/templates/design/shared/design-template-presets.ts +8 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/CHANGELOG.md +6 -0
- package/corpus/templates/slides/actions/delete-deck.ts +7 -0
- package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
- package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
+
import { fail, isActionContractError } from "@agent-native/core/action";
|
|
3
4
|
import { and, asc, eq, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
4
5
|
|
|
5
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -71,8 +72,16 @@ import {
|
|
|
71
72
|
localFolderSourceIdentityFromMetadata,
|
|
72
73
|
} from "./_local-folder-source.js";
|
|
73
74
|
export { bulkChunkSizeForColumnCount } from "./_batch-utils.js";
|
|
75
|
+
import {
|
|
76
|
+
builderExecutionPayloadReference,
|
|
77
|
+
isBuilderPrivatePayloadBoundToSource,
|
|
78
|
+
cleanupBuilderPrivatePayload,
|
|
79
|
+
enqueueBuilderPrivatePayloadCleanup,
|
|
80
|
+
readBuilderExecutionPayload,
|
|
81
|
+
} from "./_builder-cms-blob-custody.js";
|
|
74
82
|
import {
|
|
75
83
|
BuilderCmsContentEntryReadError,
|
|
84
|
+
RETRYABLE_BUILDER_SOURCE_BINDING_ERRORS,
|
|
76
85
|
readBuilderCmsContentEntryResult,
|
|
77
86
|
readBuilderCmsContentEntries,
|
|
78
87
|
readBuilderCmsModelFields,
|
|
@@ -97,6 +106,7 @@ import {
|
|
|
97
106
|
type ExistingBuilderSourceRowIdentity,
|
|
98
107
|
} from "./_builder-cms-source-adapter.js";
|
|
99
108
|
import { mergeBuilderCmsWriteSettingsIntoJson } from "./_builder-cms-write-settings.js";
|
|
109
|
+
import { assertNoActiveBuilderSourceExecutions } from "./_builder-source-execution-guard.js";
|
|
100
110
|
import { lockContentDatabaseMutation } from "./_content-database-mutation-lock.js";
|
|
101
111
|
import { ensureDocumentsFilesMembership } from "./_content-files.js";
|
|
102
112
|
import {
|
|
@@ -192,6 +202,7 @@ type SourceMetadataRecord = {
|
|
|
192
202
|
readMode?: string | null;
|
|
193
203
|
connectionId?: string | null;
|
|
194
204
|
connectionLabel?: string | null;
|
|
205
|
+
builderSpacePublicKey?: string | null;
|
|
195
206
|
truthPolicy?: ContentDatabaseSource["metadata"]["truthPolicy"];
|
|
196
207
|
syncPolicy?: "manual" | "keep_in_sync";
|
|
197
208
|
liveBridgeEnabled?: boolean;
|
|
@@ -201,9 +212,11 @@ type SourceMetadataRecord = {
|
|
|
201
212
|
lastReadMatchedRowCount?: number;
|
|
202
213
|
lastReadLimit?: number;
|
|
203
214
|
lastReadFetchedEntryCount?: number;
|
|
215
|
+
lastReadCollectionCount?: number;
|
|
204
216
|
lastReadPartial?: boolean;
|
|
205
217
|
lastReadHasMore?: boolean;
|
|
206
218
|
lastReadNextOffset?: number;
|
|
219
|
+
lastReadNextCursor?: string;
|
|
207
220
|
lastReadSuspiciousEmpty?: boolean;
|
|
208
221
|
activeReadSourceRowIds?: string[];
|
|
209
222
|
sourceFetchState?: "idle" | "fetching" | "error";
|
|
@@ -757,7 +770,7 @@ function serializeReviewEvent(
|
|
|
757
770
|
}
|
|
758
771
|
|
|
759
772
|
function serializeExecution(
|
|
760
|
-
row: ContentDatabaseSourceExecutionRowDb,
|
|
773
|
+
row: ContentDatabaseSourceExecutionRowDb & { payloadUnavailable?: true },
|
|
761
774
|
): ContentDatabaseSourceExecution {
|
|
762
775
|
return {
|
|
763
776
|
id: row.id,
|
|
@@ -767,7 +780,10 @@ function serializeExecution(
|
|
|
767
780
|
state: normalizeExecutionState(row.state),
|
|
768
781
|
idempotencyKey: row.idempotencyKey,
|
|
769
782
|
summary: row.summary,
|
|
770
|
-
payload:
|
|
783
|
+
payload: row.payloadUnavailable
|
|
784
|
+
? { unavailable: true }
|
|
785
|
+
: (parseObject<Record<string, unknown>>(row.payloadJson) ?? {}),
|
|
786
|
+
...(row.payloadUnavailable ? { payloadUnavailable: true } : {}),
|
|
771
787
|
lastError: row.lastError,
|
|
772
788
|
createdAt: row.createdAt,
|
|
773
789
|
updatedAt: row.updatedAt,
|
|
@@ -994,7 +1010,7 @@ class BuilderBodyHydrationError extends Error {
|
|
|
994
1010
|
}
|
|
995
1011
|
}
|
|
996
1012
|
|
|
997
|
-
function builderBodyHydrationFailureEvidence(error: unknown) {
|
|
1013
|
+
export function builderBodyHydrationFailureEvidence(error: unknown) {
|
|
998
1014
|
if (
|
|
999
1015
|
error instanceof BuilderCmsContentEntryReadError ||
|
|
1000
1016
|
error instanceof BuilderBodyHydrationError
|
|
@@ -1006,6 +1022,24 @@ function builderBodyHydrationFailureEvidence(error: unknown) {
|
|
|
1006
1022
|
message: error.message,
|
|
1007
1023
|
} as const;
|
|
1008
1024
|
}
|
|
1025
|
+
if (isActionContractError(error)) {
|
|
1026
|
+
return {
|
|
1027
|
+
reason:
|
|
1028
|
+
error.statusCode === 404
|
|
1029
|
+
? ("not_found" as const)
|
|
1030
|
+
: error.statusCode === 400 ||
|
|
1031
|
+
error.statusCode === 409 ||
|
|
1032
|
+
error.statusCode === 412
|
|
1033
|
+
? ("access_denied" as const)
|
|
1034
|
+
: ("transient_read_failure" as const),
|
|
1035
|
+
providerStatus: error.errorCode,
|
|
1036
|
+
retryable:
|
|
1037
|
+
RETRYABLE_BUILDER_SOURCE_BINDING_ERRORS.has(error.errorCode) ||
|
|
1038
|
+
error.statusCode === 424 ||
|
|
1039
|
+
error.statusCode >= 500,
|
|
1040
|
+
message: error.message,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1009
1043
|
return {
|
|
1010
1044
|
reason: "conversion_failed" as const,
|
|
1011
1045
|
providerStatus: "local_conversion",
|
|
@@ -1400,6 +1434,7 @@ type BuilderLiveBodyReadResult =
|
|
|
1400
1434
|
entry: null;
|
|
1401
1435
|
providerStatus:
|
|
1402
1436
|
| "http_404"
|
|
1437
|
+
| "http_200_not_found"
|
|
1403
1438
|
| "http_200_unexpected_entry"
|
|
1404
1439
|
| "mcp_not_found";
|
|
1405
1440
|
};
|
|
@@ -1411,6 +1446,8 @@ async function readBuilderEntryWithLiveBodyFromSourceRow(args: {
|
|
|
1411
1446
|
>;
|
|
1412
1447
|
sourceTable: string;
|
|
1413
1448
|
fallbackTitle: string;
|
|
1449
|
+
expectedSourceSpace?: string | null;
|
|
1450
|
+
expectedSourceConnectionId?: string | null;
|
|
1414
1451
|
}): Promise<BuilderLiveBodyReadResult> {
|
|
1415
1452
|
const sourceValues =
|
|
1416
1453
|
parseObject<Record<string, DocumentPropertyValue>>(
|
|
@@ -1420,6 +1457,9 @@ async function readBuilderEntryWithLiveBodyFromSourceRow(args: {
|
|
|
1420
1457
|
model: args.sourceTable,
|
|
1421
1458
|
entryId: args.row.sourceRowId,
|
|
1422
1459
|
strictEntryIdentity: true,
|
|
1460
|
+
expectedSourceSpace: args.expectedSourceSpace,
|
|
1461
|
+
expectedSourceConnectionId: args.expectedSourceConnectionId,
|
|
1462
|
+
requireBoundOAuth: true,
|
|
1423
1463
|
});
|
|
1424
1464
|
if (liveRead.state === "not_found") return liveRead;
|
|
1425
1465
|
const liveEntry = liveRead.entry;
|
|
@@ -1967,6 +2007,8 @@ async function processBuilderBodyHydrationJob(
|
|
|
1967
2007
|
documentContent?: string | null;
|
|
1968
2008
|
bodyHydrationVersion?: string | null;
|
|
1969
2009
|
bodyEntry?: BuilderCmsSourceEntry | null;
|
|
2010
|
+
expectedSourceSpace?: string | null;
|
|
2011
|
+
expectedSourceConnectionId?: string | null;
|
|
1970
2012
|
},
|
|
1971
2013
|
) {
|
|
1972
2014
|
const db = getDb();
|
|
@@ -2037,6 +2079,8 @@ async function processBuilderBodyHydrationJob(
|
|
|
2037
2079
|
row: sourceRow,
|
|
2038
2080
|
sourceTable: row.sourceTable,
|
|
2039
2081
|
fallbackTitle: entry.title,
|
|
2082
|
+
expectedSourceSpace: preloaded?.expectedSourceSpace,
|
|
2083
|
+
expectedSourceConnectionId: preloaded?.expectedSourceConnectionId,
|
|
2040
2084
|
});
|
|
2041
2085
|
if (liveRead.state === "not_found") {
|
|
2042
2086
|
throw new BuilderBodyHydrationError(
|
|
@@ -2144,6 +2188,8 @@ async function processBuilderBodyHydrationJob(
|
|
|
2144
2188
|
row: sourceRow,
|
|
2145
2189
|
sourceTable: row.sourceTable,
|
|
2146
2190
|
fallbackTitle: entry.title,
|
|
2191
|
+
expectedSourceSpace: preloaded?.expectedSourceSpace,
|
|
2192
|
+
expectedSourceConnectionId: preloaded?.expectedSourceConnectionId,
|
|
2147
2193
|
});
|
|
2148
2194
|
if (liveRead.state === "body") {
|
|
2149
2195
|
const liveEntry = liveRead.entry;
|
|
@@ -2887,6 +2933,13 @@ export async function processBuilderBodyHydrationQueue(args: {
|
|
|
2887
2933
|
retryFailed?: boolean;
|
|
2888
2934
|
}) {
|
|
2889
2935
|
const db = getDb();
|
|
2936
|
+
const [hydrationSource] = await db
|
|
2937
|
+
.select({ metadataJson: schema.contentDatabaseSources.metadataJson })
|
|
2938
|
+
.from(schema.contentDatabaseSources)
|
|
2939
|
+
.where(eq(schema.contentDatabaseSources.id, args.sourceId));
|
|
2940
|
+
const hydrationSourceMetadata = hydrationSource
|
|
2941
|
+
? (parseObject<SourceMetadataRecord>(hydrationSource.metadataJson) ?? {})
|
|
2942
|
+
: {};
|
|
2890
2943
|
const limit = normalizeHydrationLimit(args.limit);
|
|
2891
2944
|
const now = new Date().toISOString();
|
|
2892
2945
|
if (args.retryFailed) {
|
|
@@ -3130,6 +3183,9 @@ export async function processBuilderBodyHydrationQueue(args: {
|
|
|
3130
3183
|
model,
|
|
3131
3184
|
includeBodies: true,
|
|
3132
3185
|
limit: 10_000,
|
|
3186
|
+
expectedSourceSpace: hydrationSourceMetadata.builderSpacePublicKey,
|
|
3187
|
+
expectedSourceConnectionId: hydrationSourceMetadata.connectionId,
|
|
3188
|
+
requireBoundOAuth: true,
|
|
3133
3189
|
}),
|
|
3134
3190
|
),
|
|
3135
3191
|
);
|
|
@@ -3182,6 +3238,8 @@ export async function processBuilderBodyHydrationQueue(args: {
|
|
|
3182
3238
|
bodyHydrationVersion:
|
|
3183
3239
|
bodyHydrationVersionByItemId.get(job.databaseItemId) ?? null,
|
|
3184
3240
|
bodyEntry: bodyEntryById.get(job.sourceRowId) ?? null,
|
|
3241
|
+
expectedSourceSpace: hydrationSourceMetadata.builderSpacePublicKey,
|
|
3242
|
+
expectedSourceConnectionId: hydrationSourceMetadata.connectionId,
|
|
3185
3243
|
documentContent: documentContentById.has(job.documentId)
|
|
3186
3244
|
? (documentContentById.get(job.documentId) ?? null)
|
|
3187
3245
|
: undefined,
|
|
@@ -4023,6 +4081,7 @@ export async function getContentDatabaseSourceSnapshotForWrite(
|
|
|
4023
4081
|
? loadSourceSnapshot(source, database, {
|
|
4024
4082
|
includeHeavyBuilderBodyValues: true,
|
|
4025
4083
|
documentIds,
|
|
4084
|
+
hydrateExecutionPayloads: documentIds !== undefined,
|
|
4026
4085
|
})
|
|
4027
4086
|
: null;
|
|
4028
4087
|
}
|
|
@@ -4038,6 +4097,7 @@ export async function getContentDatabaseSourceSnapshotForWrite(
|
|
|
4038
4097
|
? loadSourceSnapshot(source, database, {
|
|
4039
4098
|
includeHeavyBuilderBodyValues: true,
|
|
4040
4099
|
documentIds,
|
|
4100
|
+
hydrateExecutionPayloads: documentIds !== undefined,
|
|
4041
4101
|
})
|
|
4042
4102
|
: null;
|
|
4043
4103
|
}
|
|
@@ -4183,6 +4243,9 @@ export async function getContentDatabaseSourceSnapshotForReview(
|
|
|
4183
4243
|
if (reviewableChanges.some((changeSet) => !changeSet.documentId)) {
|
|
4184
4244
|
return loadSourceSnapshot(source, database, {
|
|
4185
4245
|
includeHeavyBuilderBodyValues: true,
|
|
4246
|
+
hydrateExecutionPayloadsForChangeSetIds: reviewableChanges.map(
|
|
4247
|
+
(changeSet) => changeSet.id,
|
|
4248
|
+
),
|
|
4186
4249
|
});
|
|
4187
4250
|
}
|
|
4188
4251
|
const candidateDocumentIds = new Set(
|
|
@@ -4451,10 +4514,102 @@ async function loadSourceSnapshotRowsOptimistically(args: {
|
|
|
4451
4514
|
};
|
|
4452
4515
|
}
|
|
4453
4516
|
|
|
4517
|
+
async function hydrateSnapshotExecutionRow(
|
|
4518
|
+
row: ContentDatabaseSourceExecutionRowDb,
|
|
4519
|
+
): Promise<ContentDatabaseSourceExecutionRowDb> {
|
|
4520
|
+
const read = (current: ContentDatabaseSourceExecutionRowDb) =>
|
|
4521
|
+
readBuilderExecutionPayload({
|
|
4522
|
+
payloadJson: current.payloadJson,
|
|
4523
|
+
binding: {
|
|
4524
|
+
ownerEmail: current.ownerEmail,
|
|
4525
|
+
sourceId: current.sourceId,
|
|
4526
|
+
changeSetId: current.changeSetId,
|
|
4527
|
+
executionId: current.id,
|
|
4528
|
+
idempotencyKey: current.idempotencyKey,
|
|
4529
|
+
},
|
|
4530
|
+
});
|
|
4531
|
+
try {
|
|
4532
|
+
return { ...row, payloadJson: JSON.stringify(await read(row)) };
|
|
4533
|
+
} catch (error) {
|
|
4534
|
+
// Each execution state change stores a new payload blob and deletes the
|
|
4535
|
+
// previous one after commit, so a row read just before that commit can
|
|
4536
|
+
// name a deleted blob. Only a row that has since moved may be re-read.
|
|
4537
|
+
const [current] = await getDb()
|
|
4538
|
+
.select()
|
|
4539
|
+
.from(schema.contentDatabaseSourceExecutions)
|
|
4540
|
+
.where(eq(schema.contentDatabaseSourceExecutions.id, row.id))
|
|
4541
|
+
.limit(1);
|
|
4542
|
+
if (!current || current.payloadJson === row.payloadJson) throw error;
|
|
4543
|
+
return { ...current, payloadJson: JSON.stringify(await read(current)) };
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
const SNAPSHOT_EXECUTION_HYDRATION_CONCURRENCY = 4;
|
|
4548
|
+
|
|
4549
|
+
export function latestSnapshotExecutionIds(
|
|
4550
|
+
rows: Array<{ id: string; changeSetId: string }>,
|
|
4551
|
+
) {
|
|
4552
|
+
const latestIds = new Map<string, string>();
|
|
4553
|
+
for (const row of rows) latestIds.set(row.changeSetId, row.id);
|
|
4554
|
+
return new Set(latestIds.values());
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
export async function hydrateSnapshotExecutionRows(
|
|
4558
|
+
rows: ContentDatabaseSourceExecutionRowDb[],
|
|
4559
|
+
hydrate: (
|
|
4560
|
+
row: ContentDatabaseSourceExecutionRowDb,
|
|
4561
|
+
) => Promise<ContentDatabaseSourceExecutionRowDb> = hydrateSnapshotExecutionRow,
|
|
4562
|
+
shouldHydrate: (row: ContentDatabaseSourceExecutionRowDb) => boolean = () =>
|
|
4563
|
+
true,
|
|
4564
|
+
): Promise<
|
|
4565
|
+
Array<ContentDatabaseSourceExecutionRowDb & { payloadUnavailable?: true }>
|
|
4566
|
+
> {
|
|
4567
|
+
const hydrated = new Array<
|
|
4568
|
+
ContentDatabaseSourceExecutionRowDb & { payloadUnavailable?: true }
|
|
4569
|
+
>(rows.length);
|
|
4570
|
+
let nextIndex = 0;
|
|
4571
|
+
const workerCount = Math.min(
|
|
4572
|
+
SNAPSHOT_EXECUTION_HYDRATION_CONCURRENCY,
|
|
4573
|
+
rows.length,
|
|
4574
|
+
);
|
|
4575
|
+
await Promise.all(
|
|
4576
|
+
Array.from({ length: workerCount }, async () => {
|
|
4577
|
+
while (nextIndex < rows.length) {
|
|
4578
|
+
const index = nextIndex++;
|
|
4579
|
+
const row = rows[index];
|
|
4580
|
+
if (!row) continue;
|
|
4581
|
+
if (!shouldHydrate(row)) {
|
|
4582
|
+
hydrated[index] = {
|
|
4583
|
+
...row,
|
|
4584
|
+
payloadJson: JSON.stringify({ unavailable: true }),
|
|
4585
|
+
payloadUnavailable: true,
|
|
4586
|
+
};
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
try {
|
|
4590
|
+
hydrated[index] = await hydrate(row);
|
|
4591
|
+
} catch {
|
|
4592
|
+
hydrated[index] = {
|
|
4593
|
+
...row,
|
|
4594
|
+
payloadJson: JSON.stringify({ unavailable: true }),
|
|
4595
|
+
payloadUnavailable: true,
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
}),
|
|
4600
|
+
);
|
|
4601
|
+
return hydrated;
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4454
4604
|
async function loadSourceSnapshot(
|
|
4455
4605
|
source: ContentDatabaseSourceRowDb,
|
|
4456
4606
|
database: ContentDatabaseRow | ContentDatabase,
|
|
4457
|
-
options: {
|
|
4607
|
+
options: {
|
|
4608
|
+
includeHeavyBuilderBodyValues: boolean;
|
|
4609
|
+
documentIds?: string[];
|
|
4610
|
+
hydrateExecutionPayloads?: boolean;
|
|
4611
|
+
hydrateExecutionPayloadsForChangeSetIds?: string[];
|
|
4612
|
+
},
|
|
4458
4613
|
): Promise<ContentDatabaseSource> {
|
|
4459
4614
|
const db = getDb();
|
|
4460
4615
|
const [fieldRows, changeRows, propertyDefs] = await Promise.all([
|
|
@@ -4566,7 +4721,27 @@ async function loadSourceSnapshot(
|
|
|
4566
4721
|
string,
|
|
4567
4722
|
ContentDatabaseSourceExecution[]
|
|
4568
4723
|
>();
|
|
4569
|
-
|
|
4724
|
+
const hydrateAllExecutionPayloads = options.hydrateExecutionPayloads === true;
|
|
4725
|
+
const hydrateChangeSetIds =
|
|
4726
|
+
options.hydrateExecutionPayloadsForChangeSetIds !== undefined
|
|
4727
|
+
? new Set(options.hydrateExecutionPayloadsForChangeSetIds)
|
|
4728
|
+
: null;
|
|
4729
|
+
const executionIdsToHydrate = latestSnapshotExecutionIds(executionRows);
|
|
4730
|
+
const hydratedExecutionRows =
|
|
4731
|
+
hydrateAllExecutionPayloads || hydrateChangeSetIds !== null
|
|
4732
|
+
? await hydrateSnapshotExecutionRows(
|
|
4733
|
+
executionRows,
|
|
4734
|
+
undefined,
|
|
4735
|
+
(row) =>
|
|
4736
|
+
executionIdsToHydrate.has(row.id) &&
|
|
4737
|
+
(hydrateAllExecutionPayloads ||
|
|
4738
|
+
hydrateChangeSetIds?.has(row.changeSetId) === true),
|
|
4739
|
+
)
|
|
4740
|
+
: executionRows.map((row) => ({
|
|
4741
|
+
...row,
|
|
4742
|
+
payloadUnavailable: true as const,
|
|
4743
|
+
}));
|
|
4744
|
+
for (const row of hydratedExecutionRows) {
|
|
4570
4745
|
const executions = executionsByChangeSetId.get(row.changeSetId) ?? [];
|
|
4571
4746
|
executions.push(serializeExecution(row));
|
|
4572
4747
|
executionsByChangeSetId.set(row.changeSetId, executions);
|
|
@@ -4888,6 +5063,7 @@ async function loadSourceSnapshot(
|
|
|
4888
5063
|
readMode: metadata.readMode ?? null,
|
|
4889
5064
|
connectionId: metadata.connectionId ?? null,
|
|
4890
5065
|
connectionLabel: metadata.connectionLabel ?? null,
|
|
5066
|
+
builderSpacePublicKey: metadata.builderSpacePublicKey ?? null,
|
|
4891
5067
|
truthPolicy:
|
|
4892
5068
|
metadata.truthPolicy === "database_primary" ||
|
|
4893
5069
|
metadata.truthPolicy === "source_primary" ||
|
|
@@ -4922,6 +5098,10 @@ async function loadSourceSnapshot(
|
|
|
4922
5098
|
typeof metadata.lastReadFetchedEntryCount === "number"
|
|
4923
5099
|
? metadata.lastReadFetchedEntryCount
|
|
4924
5100
|
: undefined,
|
|
5101
|
+
lastReadCollectionCount:
|
|
5102
|
+
typeof metadata.lastReadCollectionCount === "number"
|
|
5103
|
+
? metadata.lastReadCollectionCount
|
|
5104
|
+
: undefined,
|
|
4925
5105
|
lastReadPartial:
|
|
4926
5106
|
typeof metadata.lastReadPartial === "boolean"
|
|
4927
5107
|
? metadata.lastReadPartial
|
|
@@ -5134,15 +5314,22 @@ export function serializeBuilderCmsSourceReadMetadataRecord(args: {
|
|
|
5134
5314
|
...existingMetadata,
|
|
5135
5315
|
builderModelFields:
|
|
5136
5316
|
args.builderModelFields ?? existingMetadata?.builderModelFields,
|
|
5317
|
+
builderSpacePublicKey:
|
|
5318
|
+
args.progress?.sourceSpacePublicKey ??
|
|
5319
|
+
existingMetadata?.builderSpacePublicKey,
|
|
5320
|
+
connectionId:
|
|
5321
|
+
args.progress?.sourceConnectionId ?? existingMetadata?.connectionId,
|
|
5137
5322
|
readMode: args.readState === "live" ? "builder-api" : "fixture",
|
|
5138
5323
|
liveReadConfigured: args.readState === "live",
|
|
5139
5324
|
lastReadEntryCount: args.entryCount,
|
|
5140
5325
|
lastReadMatchedRowCount: args.matchedRowCount,
|
|
5141
5326
|
lastReadLimit: args.progress?.requestedLimit,
|
|
5142
5327
|
lastReadFetchedEntryCount: args.progress?.fetchedEntryCount,
|
|
5328
|
+
lastReadCollectionCount: args.progress?.observedCollectionCount,
|
|
5143
5329
|
lastReadPartial: args.progress?.partial,
|
|
5144
5330
|
lastReadHasMore: args.progress?.hasMore,
|
|
5145
5331
|
lastReadNextOffset: args.progress?.nextOffset,
|
|
5332
|
+
lastReadNextCursor: args.progress?.nextCursor,
|
|
5146
5333
|
lastReadSuspiciousEmpty: args.suspiciousEmpty === true,
|
|
5147
5334
|
activeReadSourceRowIds: args.suspiciousEmpty
|
|
5148
5335
|
? []
|
|
@@ -5167,6 +5354,31 @@ export function serializeBuilderCmsSourceReadMetadataRecord(args: {
|
|
|
5167
5354
|
return JSON.stringify(metadata);
|
|
5168
5355
|
}
|
|
5169
5356
|
|
|
5357
|
+
export function assertBuilderCmsContinuationIdentity(args: {
|
|
5358
|
+
continueOffset: number;
|
|
5359
|
+
activeReadSourceRowIds: string[];
|
|
5360
|
+
entries?: Array<{ id: string }>;
|
|
5361
|
+
}) {
|
|
5362
|
+
if (args.continueOffset <= 0) return;
|
|
5363
|
+
if (
|
|
5364
|
+
new Set(args.activeReadSourceRowIds).size !==
|
|
5365
|
+
args.activeReadSourceRowIds.length ||
|
|
5366
|
+
args.activeReadSourceRowIds.length > args.continueOffset
|
|
5367
|
+
) {
|
|
5368
|
+
fail(
|
|
5369
|
+
"Builder source continuation identity exceeds its saved provider offset. Start a full refresh before mutating the snapshot.",
|
|
5370
|
+
{ errorCode: "builder_source_continuation_mismatch", statusCode: 409 },
|
|
5371
|
+
);
|
|
5372
|
+
}
|
|
5373
|
+
const activeIds = new Set(args.activeReadSourceRowIds);
|
|
5374
|
+
if (args.entries?.some((entry) => activeIds.has(entry.id))) {
|
|
5375
|
+
fail(
|
|
5376
|
+
"Builder source continuation repeated an entry from an earlier page. Start a full refresh before mutating the snapshot.",
|
|
5377
|
+
{ errorCode: "builder_source_continuation_mismatch", statusCode: 409 },
|
|
5378
|
+
);
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5170
5382
|
export function serializeSourceCapabilitiesRecord(
|
|
5171
5383
|
overrides: Partial<ContentDatabaseSourceCapabilities> = {},
|
|
5172
5384
|
) {
|
|
@@ -6183,9 +6395,60 @@ async function deleteSourceChangeSetRecords(args: {
|
|
|
6183
6395
|
)
|
|
6184
6396
|
: eq(schema.contentDatabaseSourceChangeSets.sourceId, args.sourceId);
|
|
6185
6397
|
|
|
6186
|
-
|
|
6187
|
-
await db.
|
|
6188
|
-
|
|
6398
|
+
const references = new Set<string>();
|
|
6399
|
+
await db.transaction(async (tx) => {
|
|
6400
|
+
const [source] = await tx
|
|
6401
|
+
.select({ databaseId: schema.contentDatabaseSources.databaseId })
|
|
6402
|
+
.from(schema.contentDatabaseSources)
|
|
6403
|
+
.where(eq(schema.contentDatabaseSources.id, args.sourceId));
|
|
6404
|
+
if (!source) return;
|
|
6405
|
+
await lockContentDatabaseMutation(
|
|
6406
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
6407
|
+
source.databaseId,
|
|
6408
|
+
);
|
|
6409
|
+
await assertNoActiveBuilderSourceExecutions(
|
|
6410
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
6411
|
+
[args.sourceId],
|
|
6412
|
+
args.changeSetIds,
|
|
6413
|
+
);
|
|
6414
|
+
const deletedExecutions = await tx
|
|
6415
|
+
.delete(schema.contentDatabaseSourceExecutions)
|
|
6416
|
+
.where(executionWhere)
|
|
6417
|
+
.returning({
|
|
6418
|
+
ownerEmail: schema.contentDatabaseSourceExecutions.ownerEmail,
|
|
6419
|
+
sourceId: schema.contentDatabaseSourceExecutions.sourceId,
|
|
6420
|
+
payloadJson: schema.contentDatabaseSourceExecutions.payloadJson,
|
|
6421
|
+
});
|
|
6422
|
+
for (const row of deletedExecutions) {
|
|
6423
|
+
const reference = builderExecutionPayloadReference(row.payloadJson);
|
|
6424
|
+
if (
|
|
6425
|
+
reference &&
|
|
6426
|
+
isBuilderPrivatePayloadBoundToSource(
|
|
6427
|
+
reference,
|
|
6428
|
+
row.ownerEmail,
|
|
6429
|
+
row.sourceId,
|
|
6430
|
+
)
|
|
6431
|
+
) {
|
|
6432
|
+
references.add(reference);
|
|
6433
|
+
await enqueueBuilderPrivatePayloadCleanup(
|
|
6434
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
6435
|
+
reference,
|
|
6436
|
+
);
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
await tx
|
|
6440
|
+
.delete(schema.contentDatabaseSourceChangeReviews)
|
|
6441
|
+
.where(reviewWhere);
|
|
6442
|
+
await tx
|
|
6443
|
+
.delete(schema.contentDatabaseSourceChangeSets)
|
|
6444
|
+
.where(changeSetWhere);
|
|
6445
|
+
});
|
|
6446
|
+
for (const reference of references) {
|
|
6447
|
+
await cleanupBuilderPrivatePayload(
|
|
6448
|
+
reference,
|
|
6449
|
+
"deleted source execution payload",
|
|
6450
|
+
);
|
|
6451
|
+
}
|
|
6189
6452
|
}
|
|
6190
6453
|
|
|
6191
6454
|
async function pruneDuplicateOpenSourceChangeSets(sourceId: string) {
|
|
@@ -6850,6 +7113,8 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
6850
7113
|
try {
|
|
6851
7114
|
builderModelFields = await readBuilderCmsModelFields({
|
|
6852
7115
|
model: args.source.sourceTable,
|
|
7116
|
+
expectedSourceSpace: sourceMetadata.builderSpacePublicKey,
|
|
7117
|
+
expectedSourceConnectionId: sourceMetadata.connectionId,
|
|
6853
7118
|
});
|
|
6854
7119
|
builderModelFields = mergeBuilderCmsModelFieldsPreservingReferenceModels({
|
|
6855
7120
|
existing: sourceMetadata.builderModelFields,
|
|
@@ -6873,6 +7138,8 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
6873
7138
|
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
|
|
6874
7139
|
const builderRead = await readBuilderCmsContentEntries({
|
|
6875
7140
|
model: args.source.sourceTable,
|
|
7141
|
+
expectedSourceSpace: sourceMetadata.builderSpacePublicKey,
|
|
7142
|
+
expectedSourceConnectionId: sourceMetadata.connectionId,
|
|
6876
7143
|
fieldPaths: [
|
|
6877
7144
|
...existingFields.map((field) => field.sourceFieldKey),
|
|
6878
7145
|
...projectionModelFields.map((field) => `data.${field.name}`),
|
|
@@ -6882,6 +7149,16 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
6882
7149
|
: BUILDER_CMS_REFRESH_INITIAL_PAGES,
|
|
6883
7150
|
limit: args.runFullRefresh ? 10_000 : undefined,
|
|
6884
7151
|
offset: continueOffset,
|
|
7152
|
+
afterId:
|
|
7153
|
+
continueOffset > 0 &&
|
|
7154
|
+
typeof sourceMetadata.lastReadNextCursor === "string"
|
|
7155
|
+
? sourceMetadata.lastReadNextCursor
|
|
7156
|
+
: undefined,
|
|
7157
|
+
requireBoundOAuth: true,
|
|
7158
|
+
startingFetchedEntryCount:
|
|
7159
|
+
continueOffset > 0 ? activeReadSourceRowIds.length : 0,
|
|
7160
|
+
expectedCollectionCount:
|
|
7161
|
+
continueOffset > 0 ? sourceMetadata.lastReadCollectionCount : undefined,
|
|
6885
7162
|
});
|
|
6886
7163
|
if (builderRead.state === "error") {
|
|
6887
7164
|
throw new Error(
|
|
@@ -6895,6 +7172,11 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
6895
7172
|
(builderRead.progress?.startOffset ?? 0) > 0);
|
|
6896
7173
|
const builderEntries =
|
|
6897
7174
|
builderRead.state === "live" ? builderRead.entries : [];
|
|
7175
|
+
assertBuilderCmsContinuationIdentity({
|
|
7176
|
+
continueOffset,
|
|
7177
|
+
activeReadSourceRowIds,
|
|
7178
|
+
entries: builderEntries,
|
|
7179
|
+
});
|
|
6898
7180
|
if (
|
|
6899
7181
|
args.refreshClaimId &&
|
|
6900
7182
|
!(await renewBuilderCmsSourceRefreshClaim({
|
|
@@ -42,10 +42,15 @@ import {
|
|
|
42
42
|
type ContentSidebarViewOrder,
|
|
43
43
|
type DocumentProperty,
|
|
44
44
|
} from "../shared/api.js";
|
|
45
|
+
import { contentDatabaseViewsShowPageText } from "../shared/database-item-layout.js";
|
|
45
46
|
import {
|
|
46
47
|
applyContentDatabaseTableQuery,
|
|
47
48
|
contentDatabaseTableQueryUsesProperties,
|
|
48
49
|
} from "../shared/database-query.js";
|
|
50
|
+
import {
|
|
51
|
+
buildListRowExcerpt,
|
|
52
|
+
EXCERPT_SOURCE_CHARS,
|
|
53
|
+
} from "../shared/og-description.js";
|
|
49
54
|
import {
|
|
50
55
|
evaluatePropertyFormula,
|
|
51
56
|
isBlocksPropertyType,
|
|
@@ -55,6 +60,12 @@ import {
|
|
|
55
60
|
type DocumentPropertyType,
|
|
56
61
|
} from "../shared/properties.js";
|
|
57
62
|
import { deleteBlocksFieldIdentity } from "./_blocks-field-identity.js";
|
|
63
|
+
import {
|
|
64
|
+
builderExecutionPayloadReference,
|
|
65
|
+
cleanupBuilderPrivatePayload,
|
|
66
|
+
enqueueBuilderPrivatePayloadCleanup,
|
|
67
|
+
isBuilderPrivatePayloadBoundToSource,
|
|
68
|
+
} from "./_builder-cms-blob-custody.js";
|
|
58
69
|
import { favoriteDocumentIds } from "./_content-favorites.js";
|
|
59
70
|
import {
|
|
60
71
|
listContentOrganizationMemberships,
|
|
@@ -82,6 +93,7 @@ import {
|
|
|
82
93
|
import {
|
|
83
94
|
listPropertiesForDatabaseDocuments,
|
|
84
95
|
listPropertiesForDatabase,
|
|
96
|
+
parseDatabaseViewConfig,
|
|
85
97
|
readDatabasePropertySchema,
|
|
86
98
|
serializeDatabase,
|
|
87
99
|
type DatabasePropertySchema,
|
|
@@ -228,6 +240,7 @@ type DocumentListRow = Omit<
|
|
|
228
240
|
| "content"
|
|
229
241
|
| "collabBodyRevision"
|
|
230
242
|
| "liveBodySwitchedAt"
|
|
243
|
+
| "liveCopyVersion"
|
|
231
244
|
| "createdBy"
|
|
232
245
|
| "updatedBy"
|
|
233
246
|
| "creationRequestDigest"
|
|
@@ -1009,6 +1022,13 @@ export async function getContentDatabasePageResponse(
|
|
|
1009
1022
|
});
|
|
1010
1023
|
}
|
|
1011
1024
|
|
|
1025
|
+
// List rows draw from paged reads. Unpaged reads come from menus and pickers
|
|
1026
|
+
// that never show page text, so they skip the body reads.
|
|
1027
|
+
const includeExcerpts =
|
|
1028
|
+
limit !== null &&
|
|
1029
|
+
contentDatabaseViewsShowPageText(
|
|
1030
|
+
parseDatabaseViewConfig(database.viewConfigJson).views,
|
|
1031
|
+
);
|
|
1012
1032
|
const documents =
|
|
1013
1033
|
items.length > 0
|
|
1014
1034
|
? await db
|
|
@@ -1236,12 +1256,50 @@ export async function getContentDatabasePageResponse(
|
|
|
1236
1256
|
const serializedDocumentIds = new Set(
|
|
1237
1257
|
serializedItems.map((item) => item.document.id),
|
|
1238
1258
|
);
|
|
1239
|
-
|
|
1259
|
+
// Reading a collection doesn't open every row's page, so excerpts come only
|
|
1260
|
+
// from pages the reader can view.
|
|
1261
|
+
const excerptDocumentIds = includeExcerpts
|
|
1262
|
+
? serializedItems
|
|
1263
|
+
.filter((item) => item.document.canView)
|
|
1264
|
+
.map((item) => item.document.id)
|
|
1265
|
+
: [];
|
|
1266
|
+
const [sourceSnapshots, bodyPrefixByDocumentId] = await Promise.all([
|
|
1240
1267
|
options.includeSources === false
|
|
1241
1268
|
? []
|
|
1242
|
-
:
|
|
1269
|
+
: getAllContentDatabaseSourceSnapshots(database, {
|
|
1243
1270
|
documentIds: limit !== null ? [...serializedDocumentIds] : undefined,
|
|
1244
|
-
})
|
|
1271
|
+
}),
|
|
1272
|
+
// Only the start of each returned page's body, and only when a view
|
|
1273
|
+
// shows it. Filters and sorts that run here have already cut the page.
|
|
1274
|
+
// The read takes one character more to tell a longer body from one that
|
|
1275
|
+
// fits.
|
|
1276
|
+
excerptDocumentIds.length > 0
|
|
1277
|
+
? db
|
|
1278
|
+
.select({
|
|
1279
|
+
id: schema.documents.id,
|
|
1280
|
+
bodyPrefix: sql<
|
|
1281
|
+
string | null
|
|
1282
|
+
>`substr(${schema.documents.content}, 1, ${sql.raw(String(EXCERPT_SOURCE_CHARS + 1))})`,
|
|
1283
|
+
})
|
|
1284
|
+
.from(schema.documents)
|
|
1285
|
+
.where(inArray(schema.documents.id, excerptDocumentIds))
|
|
1286
|
+
.then(
|
|
1287
|
+
(rows) =>
|
|
1288
|
+
new Map(rows.map((row) => [row.id, row.bodyPrefix ?? ""])),
|
|
1289
|
+
)
|
|
1290
|
+
: new Map<string, string>(),
|
|
1291
|
+
]);
|
|
1292
|
+
const pageItems = includeExcerpts
|
|
1293
|
+
? serializedItems.map((item) => ({
|
|
1294
|
+
...item,
|
|
1295
|
+
excerpt: item.document.canView
|
|
1296
|
+
? buildListRowExcerpt({
|
|
1297
|
+
content: bodyPrefixByDocumentId.get(item.document.id),
|
|
1298
|
+
title: item.document.title,
|
|
1299
|
+
})
|
|
1300
|
+
: "",
|
|
1301
|
+
}))
|
|
1302
|
+
: serializedItems;
|
|
1245
1303
|
const organizationVisibleDocumentIds =
|
|
1246
1304
|
organizationFilesItemFilter && sourceSnapshots.length > 0
|
|
1247
1305
|
? new Set(
|
|
@@ -1282,7 +1340,7 @@ export async function getContentDatabasePageResponse(
|
|
|
1282
1340
|
const pagedPrimary = pagedSources[0] ?? null;
|
|
1283
1341
|
|
|
1284
1342
|
const federatedItems = federateSources({
|
|
1285
|
-
items:
|
|
1343
|
+
items: pageItems,
|
|
1286
1344
|
sources: pagedSources,
|
|
1287
1345
|
});
|
|
1288
1346
|
const itemsWithOverlay = applyFederatedOverlayValues(
|
|
@@ -1639,15 +1697,40 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1639
1697
|
.select({ id: schema.contentDatabaseSources.id })
|
|
1640
1698
|
.from(schema.contentDatabaseSources)
|
|
1641
1699
|
.where(eq(schema.contentDatabaseSources.databaseId, database.id));
|
|
1700
|
+
const deletedBlobReferences = new Set<string>();
|
|
1642
1701
|
for (const source of sources) {
|
|
1643
1702
|
await db
|
|
1644
1703
|
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
1645
1704
|
.where(
|
|
1646
1705
|
eq(schema.contentDatabaseBodyHydrationQueue.sourceId, source.id),
|
|
1647
1706
|
);
|
|
1648
|
-
await db
|
|
1649
|
-
|
|
1650
|
-
|
|
1707
|
+
await db.transaction(async (tx) => {
|
|
1708
|
+
const deletedExecutions = await tx
|
|
1709
|
+
.delete(schema.contentDatabaseSourceExecutions)
|
|
1710
|
+
.where(eq(schema.contentDatabaseSourceExecutions.sourceId, source.id))
|
|
1711
|
+
.returning({
|
|
1712
|
+
ownerEmail: schema.contentDatabaseSourceExecutions.ownerEmail,
|
|
1713
|
+
payloadJson: schema.contentDatabaseSourceExecutions.payloadJson,
|
|
1714
|
+
sourceId: schema.contentDatabaseSourceExecutions.sourceId,
|
|
1715
|
+
});
|
|
1716
|
+
for (const row of deletedExecutions) {
|
|
1717
|
+
const reference = builderExecutionPayloadReference(row.payloadJson);
|
|
1718
|
+
if (
|
|
1719
|
+
reference &&
|
|
1720
|
+
isBuilderPrivatePayloadBoundToSource(
|
|
1721
|
+
reference,
|
|
1722
|
+
row.ownerEmail,
|
|
1723
|
+
row.sourceId,
|
|
1724
|
+
)
|
|
1725
|
+
) {
|
|
1726
|
+
deletedBlobReferences.add(reference);
|
|
1727
|
+
await enqueueBuilderPrivatePayloadCleanup(
|
|
1728
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
1729
|
+
reference,
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1651
1734
|
await db
|
|
1652
1735
|
.delete(schema.contentDatabaseSourceChangeReviews)
|
|
1653
1736
|
.where(
|
|
@@ -1663,6 +1746,9 @@ export async function deleteDatabaseDataForDocument(
|
|
|
1663
1746
|
.delete(schema.contentDatabaseSourceFields)
|
|
1664
1747
|
.where(eq(schema.contentDatabaseSourceFields.sourceId, source.id));
|
|
1665
1748
|
}
|
|
1749
|
+
for (const reference of deletedBlobReferences) {
|
|
1750
|
+
await cleanupBuilderPrivatePayload(reference, "deleted database source");
|
|
1751
|
+
}
|
|
1666
1752
|
await db
|
|
1667
1753
|
.delete(schema.contentDatabaseSources)
|
|
1668
1754
|
.where(eq(schema.contentDatabaseSources.databaseId, database.id));
|