@agent-native/core 0.90.1 → 0.90.2
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/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
- package/corpus/core/src/client/use-action.ts +39 -2
- package/corpus/core/src/client/use-db-sync.ts +44 -18
- package/corpus/core/src/db/create-get-db.ts +32 -2
- package/corpus/core/src/server/framework-request-handler.ts +52 -4
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
- package/corpus/templates/assets/AGENTS.md +23 -4
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
- package/corpus/templates/assets/actions/_helpers.ts +8 -1
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
- package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
- package/corpus/templates/assets/actions/generate-image.ts +342 -25
- package/corpus/templates/assets/actions/get-library.ts +4 -3
- package/corpus/templates/assets/actions/navigate.ts +2 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
- package/corpus/templates/assets/actions/update-asset.ts +1 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
- package/corpus/templates/assets/actions/view-screen.ts +8 -1
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
- package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
- package/corpus/templates/assets/app/i18n-data.ts +650 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
- package/corpus/templates/assets/app/routes/library.tsx +43 -1
- package/corpus/templates/assets/server/lib/generation.ts +366 -44
- package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
- package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
- package/corpus/templates/assets/shared/api.ts +47 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
- package/corpus/templates/clips/app/routes/record.tsx +157 -68
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
- package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
- package/corpus/templates/content/AGENTS.md +52 -52
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
- package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
- package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
- package/corpus/templates/content/actions/_database-utils.ts +44 -4
- package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
- package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
- package/corpus/templates/content/actions/delete-content-database.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +11 -2
- package/corpus/templates/content/actions/list-content-databases.ts +57 -9
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
- package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
- package/corpus/templates/content/actions/restore-content-database.ts +1 -0
- package/corpus/templates/content/actions/update-document.ts +43 -0
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
- package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
- package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
- package/corpus/templates/content/app/global.css +41 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
- package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
- package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
- package/corpus/templates/content/app/i18n-data.ts +355 -0
- package/corpus/templates/content/app/root.tsx +22 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
- package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
- package/corpus/templates/content/shared/api.ts +13 -0
- package/corpus/templates/content/shared/builder-mdx.ts +16 -3
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
- package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
- package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
- package/corpus/templates/design/AGENTS.md +9 -2
- package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
- package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
- package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
- package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
- package/corpus/templates/design/actions/create-component.ts +29 -60
- package/corpus/templates/design/actions/create-file.ts +9 -2
- package/corpus/templates/design/actions/duplicate-design.ts +31 -2
- package/corpus/templates/design/actions/edit-design.ts +36 -38
- package/corpus/templates/design/actions/generate-design-title.ts +87 -0
- package/corpus/templates/design/actions/generate-design.ts +53 -18
- package/corpus/templates/design/actions/generate-screens.ts +108 -4
- package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
- package/corpus/templates/design/actions/insert-asset.ts +34 -29
- package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
- package/corpus/templates/design/actions/present-design-variants.ts +7 -2
- package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
- package/corpus/templates/design/actions/update-file.ts +190 -75
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
- package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
- package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
- package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
- package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
- package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
- package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
- package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
- package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
- package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
- package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
- package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
- package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
- package/corpus/templates/design/app/components/design/types.ts +15 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
- package/corpus/templates/design/app/global.css +8 -8
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
- package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
- package/corpus/templates/design/app/i18n-data.ts +1664 -152
- package/corpus/templates/design/app/i18n-motion.ts +563 -0
- package/corpus/templates/design/app/lib/design-import.ts +73 -0
- package/corpus/templates/design/app/pages/Index.tsx +41 -22
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
- package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
- package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
- package/corpus/templates/design/server/source-workspace.ts +139 -59
- package/corpus/templates/design/shared/board-file.ts +11 -2
- package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
- package/corpus/templates/design/shared/canvas-math.ts +354 -35
- package/corpus/templates/design/shared/code-layer.ts +277 -8
- package/corpus/templates/design/shared/design-state.ts +11 -4
- package/corpus/templates/design/shared/interaction-states.ts +548 -0
- package/corpus/templates/design/shared/motion-compiler.ts +186 -23
- package/corpus/templates/design/shared/motion-easing.ts +294 -0
- package/corpus/templates/design/shared/motion-timeline.ts +400 -34
- package/corpus/templates/design/shared/prompt-title.ts +95 -0
- package/corpus/templates/design/shared/responsive-classes.ts +448 -10
- package/corpus/templates/design/shared/screen-annotation.ts +46 -0
- package/corpus/templates/design/shared/shader-fills.ts +1009 -0
- package/corpus/templates/design/shared/shader-presets.ts +852 -4
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
- package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
- package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/client/use-action.d.ts +3 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +34 -3
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +4 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +37 -18
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/create-get-db.d.ts +25 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +25 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +37 -1
- package/dist/server/framework-request-handler.js.map +1 -1
- package/package.json +1 -1
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
- package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deleteCollabState, releaseDoc } from "@agent-native/core/collab";
|
|
2
|
-
import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
|
|
2
|
+
import { and, asc, eq, inArray, isNull, notInArray, sql } from "drizzle-orm";
|
|
3
3
|
|
|
4
4
|
import { getDb, schema } from "../server/db/index.js";
|
|
5
5
|
import type {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
builderMdxBodyToBuilderBlocks,
|
|
43
43
|
} from "../shared/builder-mdx.js";
|
|
44
44
|
import {
|
|
45
|
+
normalizePropertyValue,
|
|
45
46
|
parsePropertyOptions,
|
|
46
47
|
serializePropertyOptions,
|
|
47
48
|
serializePropertyValue,
|
|
@@ -49,8 +50,10 @@ import {
|
|
|
49
50
|
} from "../shared/properties.js";
|
|
50
51
|
import { sanitizeNormalizationFormula } from "../shared/properties.js";
|
|
51
52
|
import {
|
|
53
|
+
readBuilderCmsContentEntry,
|
|
52
54
|
readBuilderCmsContentEntries,
|
|
53
55
|
readBuilderCmsModelFields,
|
|
56
|
+
type BuilderCmsReadProgress,
|
|
54
57
|
type BuilderCmsReadState,
|
|
55
58
|
} from "./_builder-cms-read-client.js";
|
|
56
59
|
import {
|
|
@@ -70,6 +73,7 @@ import {
|
|
|
70
73
|
} from "./_builder-cms-source-adapter.js";
|
|
71
74
|
import { mergeBuilderCmsWriteSettingsIntoJson } from "./_builder-cms-write-settings.js";
|
|
72
75
|
import { listPropertiesForDatabase, nanoid } from "./_property-utils.js";
|
|
76
|
+
import { isEffectivelyEmptyDocumentContent } from "./update-document.js";
|
|
73
77
|
|
|
74
78
|
type ContentDatabaseRow = typeof schema.contentDatabases.$inferSelect;
|
|
75
79
|
type ContentDatabaseSourceRowDb =
|
|
@@ -116,6 +120,13 @@ type SourceMetadataRecord = {
|
|
|
116
120
|
liveReadConfigured?: boolean;
|
|
117
121
|
lastReadEntryCount?: number;
|
|
118
122
|
lastReadMatchedRowCount?: number;
|
|
123
|
+
lastReadLimit?: number;
|
|
124
|
+
lastReadFetchedEntryCount?: number;
|
|
125
|
+
lastReadPartial?: boolean;
|
|
126
|
+
lastReadHasMore?: boolean;
|
|
127
|
+
lastReadNextOffset?: number;
|
|
128
|
+
activeReadSourceRowIds?: string[];
|
|
129
|
+
sourceFetchState?: "idle" | "fetching" | "error" | string;
|
|
119
130
|
allowDraftWrites?: boolean;
|
|
120
131
|
allowPublishWrites?: boolean;
|
|
121
132
|
allowedWriteModes?: ContentDatabaseSourcePushMode[];
|
|
@@ -464,6 +475,17 @@ function sameSourceFieldValue(a: unknown, b: unknown): boolean {
|
|
|
464
475
|
return normalize(a) === normalize(b);
|
|
465
476
|
}
|
|
466
477
|
|
|
478
|
+
function sameMappedSourceFieldValue(
|
|
479
|
+
localValue: unknown,
|
|
480
|
+
sourceValue: unknown,
|
|
481
|
+
type: DocumentProperty["definition"]["type"] | null | undefined,
|
|
482
|
+
): boolean {
|
|
483
|
+
const normalizedSourceValue = type
|
|
484
|
+
? normalizePropertyValue(type, sourceValue)
|
|
485
|
+
: sourceValue;
|
|
486
|
+
return sameSourceFieldValue(localValue, normalizedSourceValue);
|
|
487
|
+
}
|
|
488
|
+
|
|
467
489
|
function stringSourceValue(
|
|
468
490
|
values: Record<string, DocumentPropertyValue>,
|
|
469
491
|
key: string,
|
|
@@ -477,10 +499,53 @@ function bodyExcerpt(value: string | null | undefined) {
|
|
|
477
499
|
return excerpt || null;
|
|
478
500
|
}
|
|
479
501
|
|
|
502
|
+
function normalizeBuilderBodyBaselineContent(value: string | null | undefined) {
|
|
503
|
+
return (value ?? "").replace(/\r\n/g, "\n").trim();
|
|
504
|
+
}
|
|
505
|
+
|
|
480
506
|
const BUILDER_BODY_HYDRATION_BACKGROUND_PRIORITY = 10;
|
|
481
507
|
const BUILDER_BODY_HYDRATION_OPEN_PRIORITY = 0;
|
|
482
508
|
const BUILDER_BODY_HYDRATION_BATCH_LIMIT = 8;
|
|
483
|
-
const
|
|
509
|
+
const BUILDER_BODY_HYDRATION_MAX_ATTEMPTS = 5;
|
|
510
|
+
const BUILDER_BODY_HYDRATION_CODEC_VERSION = "readable-native-images-v5";
|
|
511
|
+
const BUILDER_CMS_REFRESH_INITIAL_PAGES = 1;
|
|
512
|
+
const BUILDER_BODY_NOT_AVAILABLE_ERROR = "body not yet available from Builder";
|
|
513
|
+
|
|
514
|
+
function builderBodyHydrationDelayMs() {
|
|
515
|
+
if (
|
|
516
|
+
process.env.NODE_ENV === "production" ||
|
|
517
|
+
process.env.CI ||
|
|
518
|
+
process.env.VITEST
|
|
519
|
+
) {
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
const parsed = Number(process.env.BUILDER_BODY_HYDRATION_DELAY_MS ?? "0");
|
|
523
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function sleep(ms: number) {
|
|
527
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function builderBodyHydrationPriorityForRequest(args: {
|
|
531
|
+
documentId?: string | null;
|
|
532
|
+
}) {
|
|
533
|
+
return args.documentId
|
|
534
|
+
? BUILDER_BODY_HYDRATION_OPEN_PRIORITY
|
|
535
|
+
: BUILDER_BODY_HYDRATION_BACKGROUND_PRIORITY;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function sortBuilderBodyHydrationQueueForProcessing<
|
|
539
|
+
T extends { priority: number; createdAt: string },
|
|
540
|
+
>(rows: T[]): T[] {
|
|
541
|
+
return [...rows].sort(
|
|
542
|
+
(a, b) => a.priority - b.priority || a.createdAt.localeCompare(b.createdAt),
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function builderBodyHydrationAttemptIsTerminal(attempts: number) {
|
|
547
|
+
return attempts >= BUILDER_BODY_HYDRATION_MAX_ATTEMPTS;
|
|
548
|
+
}
|
|
484
549
|
|
|
485
550
|
async function builderBodySnapshotForEntry(entry: BuilderCmsSourceEntry) {
|
|
486
551
|
if (!entry.rawEntry) return null;
|
|
@@ -615,6 +680,13 @@ function builderStoredBodyIsStale(args: {
|
|
|
615
680
|
);
|
|
616
681
|
}
|
|
617
682
|
|
|
683
|
+
function builderEntryHasBodyContent(entry: BuilderCmsSourceEntry | null) {
|
|
684
|
+
return !!stringSourceValue(
|
|
685
|
+
entry?.sourceValues ?? {},
|
|
686
|
+
BUILDER_CMS_BODY_CONTENT_KEY,
|
|
687
|
+
)?.trim();
|
|
688
|
+
}
|
|
689
|
+
|
|
618
690
|
async function readableBuilderBodyFromStoredLossless(args: {
|
|
619
691
|
losslessContent: string | null;
|
|
620
692
|
sidecarsJson: string | null;
|
|
@@ -688,6 +760,37 @@ function builderEntryFromSourceRow(args: {
|
|
|
688
760
|
};
|
|
689
761
|
}
|
|
690
762
|
|
|
763
|
+
async function readBuilderEntryWithLiveBodyFromSourceRow(args: {
|
|
764
|
+
row: Pick<
|
|
765
|
+
ContentDatabaseSourceRecordRowDb,
|
|
766
|
+
"sourceRowId" | "sourceValuesJson"
|
|
767
|
+
>;
|
|
768
|
+
sourceTable: string;
|
|
769
|
+
fallbackTitle: string;
|
|
770
|
+
}): Promise<BuilderCmsSourceEntry | null> {
|
|
771
|
+
const sourceValues =
|
|
772
|
+
parseObject<Record<string, DocumentPropertyValue>>(
|
|
773
|
+
args.row.sourceValuesJson,
|
|
774
|
+
) ?? {};
|
|
775
|
+
const liveEntry = await readBuilderCmsContentEntry({
|
|
776
|
+
model: args.sourceTable,
|
|
777
|
+
entryId: args.row.sourceRowId,
|
|
778
|
+
}).catch(() => null);
|
|
779
|
+
if (!liveEntry || liveEntry.id !== args.row.sourceRowId) return null;
|
|
780
|
+
const entryWithStoredValues = {
|
|
781
|
+
...liveEntry,
|
|
782
|
+
title: liveEntry.title || args.fallbackTitle,
|
|
783
|
+
sourceValues: {
|
|
784
|
+
...sourceValues,
|
|
785
|
+
...liveEntry.sourceValues,
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
const refreshedEntry = await refreshBuilderBodySourceValuesFromStoredLossless(
|
|
789
|
+
await withBuilderBodySourceValues(entryWithStoredValues),
|
|
790
|
+
);
|
|
791
|
+
return builderEntryHasBodyContent(refreshedEntry) ? refreshedEntry : null;
|
|
792
|
+
}
|
|
793
|
+
|
|
691
794
|
export async function enqueueBuilderBodyHydration(args: {
|
|
692
795
|
sourceId: string;
|
|
693
796
|
ownerEmail: string;
|
|
@@ -701,7 +804,9 @@ export async function enqueueBuilderBodyHydration(args: {
|
|
|
701
804
|
}) {
|
|
702
805
|
const db = getDb();
|
|
703
806
|
const sourceRowId = args.entry.id;
|
|
704
|
-
const priority =
|
|
807
|
+
const priority =
|
|
808
|
+
args.priority ??
|
|
809
|
+
builderBodyHydrationPriorityForRequest({ documentId: null });
|
|
705
810
|
const [existing] = await db
|
|
706
811
|
.select()
|
|
707
812
|
.from(schema.contentDatabaseBodyHydrationQueue)
|
|
@@ -711,6 +816,13 @@ export async function enqueueBuilderBodyHydration(args: {
|
|
|
711
816
|
args.databaseItemId,
|
|
712
817
|
),
|
|
713
818
|
);
|
|
819
|
+
const existingEntry = existing ? parseHydrationEntry(existing) : null;
|
|
820
|
+
const shouldPreserveExistingEntry =
|
|
821
|
+
builderEntryHasBodyContent(existingEntry) &&
|
|
822
|
+
!builderEntryHasBodyContent(args.entry);
|
|
823
|
+
const sourceEntryJson = shouldPreserveExistingEntry
|
|
824
|
+
? existing!.sourceEntryJson
|
|
825
|
+
: JSON.stringify(args.entry);
|
|
714
826
|
const values = {
|
|
715
827
|
ownerEmail: args.ownerEmail,
|
|
716
828
|
orgId: args.orgId,
|
|
@@ -719,7 +831,7 @@ export async function enqueueBuilderBodyHydration(args: {
|
|
|
719
831
|
documentId: args.documentId,
|
|
720
832
|
sourceRowId,
|
|
721
833
|
sourceTable: args.sourceTable,
|
|
722
|
-
sourceEntryJson
|
|
834
|
+
sourceEntryJson,
|
|
723
835
|
priority: Math.min(existing?.priority ?? priority, priority),
|
|
724
836
|
lastError: null,
|
|
725
837
|
updatedAt: args.now,
|
|
@@ -764,6 +876,7 @@ export async function enqueueBuilderBodyHydrationForItems(args: {
|
|
|
764
876
|
if (
|
|
765
877
|
item.bodyHydration?.status === "hydrated" &&
|
|
766
878
|
item.bodyHydration.version === builderBodyHydrationVersion(entry) &&
|
|
879
|
+
!isEffectivelyEmptyDocumentContent(item.document.content) &&
|
|
767
880
|
!builderBodyIsRawPlaceholderOnly(item.document.content)
|
|
768
881
|
) {
|
|
769
882
|
continue;
|
|
@@ -782,7 +895,63 @@ export async function enqueueBuilderBodyHydrationForItems(args: {
|
|
|
782
895
|
void processBuilderBodyHydrationQueue({
|
|
783
896
|
sourceId: args.sourceId,
|
|
784
897
|
limit: BUILDER_BODY_HYDRATION_BATCH_LIMIT,
|
|
785
|
-
}).catch(() =>
|
|
898
|
+
}).catch((error) => {
|
|
899
|
+
console.error("Builder body hydration background kick failed", error);
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async function enqueueEmptyHydratedBuilderBodiesFromStoredRows(args: {
|
|
904
|
+
source: ContentDatabaseSourceRowDb;
|
|
905
|
+
now: string;
|
|
906
|
+
}) {
|
|
907
|
+
const db = getDb();
|
|
908
|
+
const rows = await db
|
|
909
|
+
.select({
|
|
910
|
+
item: schema.contentDatabaseItems,
|
|
911
|
+
sourceRow: schema.contentDatabaseSourceRows,
|
|
912
|
+
document: schema.documents,
|
|
913
|
+
})
|
|
914
|
+
.from(schema.contentDatabaseSourceRows)
|
|
915
|
+
.innerJoin(
|
|
916
|
+
schema.contentDatabaseItems,
|
|
917
|
+
eq(
|
|
918
|
+
schema.contentDatabaseItems.id,
|
|
919
|
+
schema.contentDatabaseSourceRows.databaseItemId,
|
|
920
|
+
),
|
|
921
|
+
)
|
|
922
|
+
.innerJoin(
|
|
923
|
+
schema.documents,
|
|
924
|
+
eq(schema.documents.id, schema.contentDatabaseSourceRows.documentId),
|
|
925
|
+
)
|
|
926
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
|
|
927
|
+
for (const row of rows) {
|
|
928
|
+
if (
|
|
929
|
+
row.item.bodyHydrationStatus !== "hydrated" &&
|
|
930
|
+
row.item.bodyHydrationStatus !== "pending"
|
|
931
|
+
)
|
|
932
|
+
continue;
|
|
933
|
+
if (!isEffectivelyEmptyDocumentContent(row.document.content)) continue;
|
|
934
|
+
const entry = builderEntryFromSourceRow({
|
|
935
|
+
row: row.sourceRow,
|
|
936
|
+
sourceTable: args.source.sourceTable,
|
|
937
|
+
fallbackTitle: row.document.title,
|
|
938
|
+
});
|
|
939
|
+
const refreshedEntry = entry
|
|
940
|
+
? await refreshBuilderBodySourceValuesFromStoredLossless(entry)
|
|
941
|
+
: null;
|
|
942
|
+
if (!refreshedEntry || !builderEntryHasBodyContent(refreshedEntry))
|
|
943
|
+
continue;
|
|
944
|
+
await enqueueBuilderBodyHydration({
|
|
945
|
+
sourceId: args.source.id,
|
|
946
|
+
ownerEmail: row.item.ownerEmail,
|
|
947
|
+
orgId: row.item.orgId,
|
|
948
|
+
databaseItemId: row.item.id,
|
|
949
|
+
documentId: row.item.documentId,
|
|
950
|
+
sourceTable: args.source.sourceTable,
|
|
951
|
+
entry: refreshedEntry,
|
|
952
|
+
now: args.now,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
786
955
|
}
|
|
787
956
|
|
|
788
957
|
function parseHydrationEntry(
|
|
@@ -829,34 +998,152 @@ async function processBuilderBodyHydrationJob(
|
|
|
829
998
|
|
|
830
999
|
const entry = parseHydrationEntry(row);
|
|
831
1000
|
if (!entry) throw new Error("Builder body hydration entry is missing.");
|
|
832
|
-
|
|
1001
|
+
let activeSourceEntryJson = row.sourceEntryJson;
|
|
1002
|
+
let entryWithBody = await refreshBuilderBodySourceValuesFromStoredLossless(
|
|
833
1003
|
await withBuilderBodySourceValues(entry),
|
|
834
1004
|
);
|
|
1005
|
+
const [sourceRow] = await db
|
|
1006
|
+
.select()
|
|
1007
|
+
.from(schema.contentDatabaseSourceRows)
|
|
1008
|
+
.where(
|
|
1009
|
+
and(
|
|
1010
|
+
eq(schema.contentDatabaseSourceRows.sourceId, row.sourceId),
|
|
1011
|
+
eq(schema.contentDatabaseSourceRows.databaseItemId, row.databaseItemId),
|
|
1012
|
+
),
|
|
1013
|
+
);
|
|
835
1014
|
const sourceValues =
|
|
836
1015
|
parseObject<Record<string, DocumentPropertyValue>>(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1016
|
+
sourceRow?.sourceValuesJson ?? "{}",
|
|
1017
|
+
) ?? {};
|
|
1018
|
+
let nextValues = {
|
|
1019
|
+
...sourceValues,
|
|
1020
|
+
...entryWithBody.sourceValues,
|
|
1021
|
+
};
|
|
1022
|
+
let nextContent =
|
|
1023
|
+
stringSourceValue(nextValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
|
|
1024
|
+
if (!nextContent.trim()) {
|
|
1025
|
+
const rebuiltBaseEntry = sourceRow
|
|
1026
|
+
? builderEntryFromSourceRow({
|
|
1027
|
+
row: sourceRow,
|
|
1028
|
+
sourceTable: row.sourceTable,
|
|
1029
|
+
fallbackTitle: entry.title,
|
|
1030
|
+
})
|
|
1031
|
+
: null;
|
|
1032
|
+
const rebuiltEntry = rebuiltBaseEntry
|
|
1033
|
+
? await refreshBuilderBodySourceValuesFromStoredLossless(rebuiltBaseEntry)
|
|
1034
|
+
: null;
|
|
1035
|
+
if (rebuiltEntry) {
|
|
1036
|
+
const rebuiltValues = {
|
|
1037
|
+
...sourceValues,
|
|
1038
|
+
...rebuiltEntry.sourceValues,
|
|
1039
|
+
};
|
|
1040
|
+
const rebuiltContent =
|
|
1041
|
+
stringSourceValue(rebuiltValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
|
|
1042
|
+
if (rebuiltContent.trim()) {
|
|
1043
|
+
const rebuiltSourceEntryJson = JSON.stringify(rebuiltEntry);
|
|
1044
|
+
const [upgraded] = await db
|
|
1045
|
+
.update(schema.contentDatabaseBodyHydrationQueue)
|
|
1046
|
+
.set({
|
|
1047
|
+
sourceEntryJson: rebuiltSourceEntryJson,
|
|
1048
|
+
lastError: null,
|
|
1049
|
+
updatedAt: now,
|
|
841
1050
|
})
|
|
842
|
-
.from(schema.contentDatabaseSourceRows)
|
|
843
1051
|
.where(
|
|
844
1052
|
and(
|
|
845
|
-
eq(schema.
|
|
1053
|
+
eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
|
|
846
1054
|
eq(
|
|
847
|
-
schema.
|
|
848
|
-
|
|
1055
|
+
schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
|
|
1056
|
+
activeSourceEntryJson,
|
|
849
1057
|
),
|
|
850
1058
|
),
|
|
851
1059
|
)
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1060
|
+
.returning({ id: schema.contentDatabaseBodyHydrationQueue.id });
|
|
1061
|
+
if (!upgraded) return;
|
|
1062
|
+
activeSourceEntryJson = rebuiltSourceEntryJson;
|
|
1063
|
+
entryWithBody = rebuiltEntry;
|
|
1064
|
+
nextValues = rebuiltValues;
|
|
1065
|
+
nextContent = rebuiltContent;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (!nextContent.trim() && sourceRow) {
|
|
1069
|
+
const liveEntry = await readBuilderEntryWithLiveBodyFromSourceRow({
|
|
1070
|
+
row: sourceRow,
|
|
1071
|
+
sourceTable: row.sourceTable,
|
|
1072
|
+
fallbackTitle: entry.title,
|
|
1073
|
+
});
|
|
1074
|
+
if (liveEntry) {
|
|
1075
|
+
const liveValues = {
|
|
1076
|
+
...sourceValues,
|
|
1077
|
+
...liveEntry.sourceValues,
|
|
1078
|
+
};
|
|
1079
|
+
const liveContent =
|
|
1080
|
+
stringSourceValue(liveValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
|
|
1081
|
+
if (liveContent.trim()) {
|
|
1082
|
+
const liveSourceEntryJson = JSON.stringify(liveEntry);
|
|
1083
|
+
const [upgraded] = await db
|
|
1084
|
+
.update(schema.contentDatabaseBodyHydrationQueue)
|
|
1085
|
+
.set({
|
|
1086
|
+
sourceEntryJson: liveSourceEntryJson,
|
|
1087
|
+
lastError: null,
|
|
1088
|
+
updatedAt: now,
|
|
1089
|
+
})
|
|
1090
|
+
.where(
|
|
1091
|
+
and(
|
|
1092
|
+
eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
|
|
1093
|
+
eq(
|
|
1094
|
+
schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
|
|
1095
|
+
activeSourceEntryJson,
|
|
1096
|
+
),
|
|
1097
|
+
),
|
|
1098
|
+
)
|
|
1099
|
+
.returning({ id: schema.contentDatabaseBodyHydrationQueue.id });
|
|
1100
|
+
if (!upgraded) return;
|
|
1101
|
+
activeSourceEntryJson = liveSourceEntryJson;
|
|
1102
|
+
entryWithBody = liveEntry;
|
|
1103
|
+
nextValues = liveValues;
|
|
1104
|
+
nextContent = liveContent;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (!nextContent.trim()) {
|
|
1109
|
+
const attempts = row.attempts + 1;
|
|
1110
|
+
await db.transaction(async (tx) => {
|
|
1111
|
+
if (builderBodyHydrationAttemptIsTerminal(attempts)) {
|
|
1112
|
+
await tx
|
|
1113
|
+
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
1114
|
+
.where(
|
|
1115
|
+
and(
|
|
1116
|
+
eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
|
|
1117
|
+
eq(
|
|
1118
|
+
schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
|
|
1119
|
+
activeSourceEntryJson,
|
|
1120
|
+
),
|
|
1121
|
+
),
|
|
1122
|
+
);
|
|
1123
|
+
await tx
|
|
1124
|
+
.update(schema.contentDatabaseItems)
|
|
1125
|
+
.set({
|
|
1126
|
+
bodyHydrationStatus: "error",
|
|
1127
|
+
bodyHydrationAttemptedAt: now,
|
|
1128
|
+
bodyHydrationError: BUILDER_BODY_NOT_AVAILABLE_ERROR,
|
|
1129
|
+
updatedAt: now,
|
|
1130
|
+
})
|
|
1131
|
+
.where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
await tx
|
|
1135
|
+
.update(schema.contentDatabaseItems)
|
|
1136
|
+
.set({
|
|
1137
|
+
bodyHydrationStatus: "pending",
|
|
1138
|
+
bodyHydrationAttemptedAt: now,
|
|
1139
|
+
bodyHydrationError: null,
|
|
1140
|
+
updatedAt: now,
|
|
1141
|
+
})
|
|
1142
|
+
.where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
|
|
1143
|
+
});
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
860
1147
|
const [document] = await db
|
|
861
1148
|
.select({ content: schema.documents.content })
|
|
862
1149
|
.from(schema.documents)
|
|
@@ -866,6 +1153,7 @@ async function processBuilderBodyHydrationJob(
|
|
|
866
1153
|
const currentContent = document?.content ?? "";
|
|
867
1154
|
const shouldWriteBody =
|
|
868
1155
|
currentContent === "" ||
|
|
1156
|
+
isEffectivelyEmptyDocumentContent(currentContent) ||
|
|
869
1157
|
currentContent === previousContent ||
|
|
870
1158
|
currentContent.trim() === "" ||
|
|
871
1159
|
builderBodyIsRawPlaceholderOnly(currentContent) ||
|
|
@@ -877,30 +1165,24 @@ async function processBuilderBodyHydrationJob(
|
|
|
877
1165
|
let wroteBody = false;
|
|
878
1166
|
await db.transaction(async (tx) => {
|
|
879
1167
|
if (shouldWriteBody) {
|
|
1168
|
+
const contentCas =
|
|
1169
|
+
isEffectivelyEmptyDocumentContent(currentContent) &&
|
|
1170
|
+
isEffectivelyEmptyDocumentContent(previousContent)
|
|
1171
|
+
? inArray(schema.documents.content, ["", "<empty-block/>"])
|
|
1172
|
+
: eq(schema.documents.content, currentContent);
|
|
880
1173
|
const [updatedDocument] = await tx
|
|
881
1174
|
.update(schema.documents)
|
|
882
1175
|
.set({ content: nextContent, updatedAt: now })
|
|
883
|
-
.where(
|
|
884
|
-
and(
|
|
885
|
-
eq(schema.documents.id, row.documentId),
|
|
886
|
-
eq(schema.documents.content, currentContent),
|
|
887
|
-
),
|
|
888
|
-
)
|
|
1176
|
+
.where(and(eq(schema.documents.id, row.documentId), contentCas))
|
|
889
1177
|
.returning({ id: schema.documents.id });
|
|
890
1178
|
wroteBody = Boolean(updatedDocument);
|
|
891
1179
|
}
|
|
892
1180
|
if (shouldWriteBody && !wroteBody) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
bodyHydrationError:
|
|
899
|
-
"Skipped Builder body hydration because the document changed during sync.",
|
|
900
|
-
updatedAt: now,
|
|
901
|
-
})
|
|
902
|
-
.where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
|
|
903
|
-
await tx
|
|
1181
|
+
// Only mark the item pending if our queue row still exists. If a
|
|
1182
|
+
// concurrent processor already completed (and deleted) this job, it
|
|
1183
|
+
// owns the final `hydrated` status — resetting to pending here would
|
|
1184
|
+
// strand the item as a zombie (pending with no queue row).
|
|
1185
|
+
const [stillQueued] = await tx
|
|
904
1186
|
.update(schema.contentDatabaseBodyHydrationQueue)
|
|
905
1187
|
.set({
|
|
906
1188
|
lastError:
|
|
@@ -912,10 +1194,23 @@ async function processBuilderBodyHydrationJob(
|
|
|
912
1194
|
eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
|
|
913
1195
|
eq(
|
|
914
1196
|
schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
|
|
915
|
-
|
|
1197
|
+
activeSourceEntryJson,
|
|
916
1198
|
),
|
|
917
1199
|
),
|
|
918
|
-
)
|
|
1200
|
+
)
|
|
1201
|
+
.returning({ id: schema.contentDatabaseBodyHydrationQueue.id });
|
|
1202
|
+
if (stillQueued) {
|
|
1203
|
+
await tx
|
|
1204
|
+
.update(schema.contentDatabaseItems)
|
|
1205
|
+
.set({
|
|
1206
|
+
bodyHydrationStatus: "pending",
|
|
1207
|
+
bodyHydrationAttemptedAt: now,
|
|
1208
|
+
bodyHydrationError:
|
|
1209
|
+
"Skipped Builder body hydration because the document changed during sync.",
|
|
1210
|
+
updatedAt: now,
|
|
1211
|
+
})
|
|
1212
|
+
.where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
|
|
1213
|
+
}
|
|
919
1214
|
return;
|
|
920
1215
|
}
|
|
921
1216
|
const [deleted] = await tx
|
|
@@ -925,7 +1220,7 @@ async function processBuilderBodyHydrationJob(
|
|
|
925
1220
|
eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
|
|
926
1221
|
eq(
|
|
927
1222
|
schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
|
|
928
|
-
|
|
1223
|
+
activeSourceEntryJson,
|
|
929
1224
|
),
|
|
930
1225
|
),
|
|
931
1226
|
)
|
|
@@ -937,7 +1232,7 @@ async function processBuilderBodyHydrationJob(
|
|
|
937
1232
|
.set({
|
|
938
1233
|
sourceValuesJson: JSON.stringify(nextValues),
|
|
939
1234
|
lastSyncedAt: now,
|
|
940
|
-
lastSourceUpdatedAt:
|
|
1235
|
+
lastSourceUpdatedAt: entryWithBody.updatedAt ?? now,
|
|
941
1236
|
updatedAt: now,
|
|
942
1237
|
})
|
|
943
1238
|
.where(
|
|
@@ -950,15 +1245,26 @@ async function processBuilderBodyHydrationJob(
|
|
|
950
1245
|
),
|
|
951
1246
|
);
|
|
952
1247
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
.where(eq(schema.
|
|
1248
|
+
// Our delete matched nothing: either a NEWER job version replaced this
|
|
1249
|
+
// row (same id, different sourceEntryJson — mark pending so the newer
|
|
1250
|
+
// job's processor owns it), or a concurrent processor completed and
|
|
1251
|
+
// deleted the job — in which case it already set `hydrated`, and
|
|
1252
|
+
// resetting to pending would strand the item with no queue row.
|
|
1253
|
+
const [replacedByNewerJob] = await tx
|
|
1254
|
+
.select({ id: schema.contentDatabaseBodyHydrationQueue.id })
|
|
1255
|
+
.from(schema.contentDatabaseBodyHydrationQueue)
|
|
1256
|
+
.where(eq(schema.contentDatabaseBodyHydrationQueue.id, row.id));
|
|
1257
|
+
if (replacedByNewerJob) {
|
|
1258
|
+
await tx
|
|
1259
|
+
.update(schema.contentDatabaseItems)
|
|
1260
|
+
.set({
|
|
1261
|
+
bodyHydrationStatus: "pending",
|
|
1262
|
+
bodyHydrationAttemptedAt: now,
|
|
1263
|
+
bodyHydrationError: null,
|
|
1264
|
+
updatedAt: now,
|
|
1265
|
+
})
|
|
1266
|
+
.where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
|
|
1267
|
+
}
|
|
962
1268
|
return;
|
|
963
1269
|
}
|
|
964
1270
|
await tx
|
|
@@ -966,7 +1272,7 @@ async function processBuilderBodyHydrationJob(
|
|
|
966
1272
|
.set({
|
|
967
1273
|
sourceValuesJson: JSON.stringify(nextValues),
|
|
968
1274
|
lastSyncedAt: now,
|
|
969
|
-
lastSourceUpdatedAt:
|
|
1275
|
+
lastSourceUpdatedAt: entryWithBody.updatedAt ?? now,
|
|
970
1276
|
updatedAt: now,
|
|
971
1277
|
})
|
|
972
1278
|
.where(
|
|
@@ -1130,11 +1436,14 @@ export async function processBuilderBodyHydrationQueue(args: {
|
|
|
1130
1436
|
for (const job of jobs) {
|
|
1131
1437
|
const attemptNow = new Date().toISOString();
|
|
1132
1438
|
try {
|
|
1439
|
+
const delayMs = builderBodyHydrationDelayMs();
|
|
1440
|
+
if (delayMs > 0) await sleep(delayMs);
|
|
1133
1441
|
await processBuilderBodyHydrationJob(job, attemptNow);
|
|
1134
1442
|
succeeded += 1;
|
|
1135
1443
|
} catch (error) {
|
|
1136
1444
|
failed += 1;
|
|
1137
1445
|
const message = error instanceof Error ? error.message : String(error);
|
|
1446
|
+
const attempts = job.attempts + 1;
|
|
1138
1447
|
await db
|
|
1139
1448
|
.update(schema.contentDatabaseItems)
|
|
1140
1449
|
.set({
|
|
@@ -1144,10 +1453,16 @@ export async function processBuilderBodyHydrationQueue(args: {
|
|
|
1144
1453
|
updatedAt: attemptNow,
|
|
1145
1454
|
})
|
|
1146
1455
|
.where(eq(schema.contentDatabaseItems.id, job.databaseItemId));
|
|
1456
|
+
if (builderBodyHydrationAttemptIsTerminal(attempts)) {
|
|
1457
|
+
await db
|
|
1458
|
+
.delete(schema.contentDatabaseBodyHydrationQueue)
|
|
1459
|
+
.where(eq(schema.contentDatabaseBodyHydrationQueue.id, job.id));
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1147
1462
|
await db
|
|
1148
1463
|
.update(schema.contentDatabaseBodyHydrationQueue)
|
|
1149
1464
|
.set({
|
|
1150
|
-
attempts
|
|
1465
|
+
attempts,
|
|
1151
1466
|
lastAttemptedAt: attemptNow,
|
|
1152
1467
|
lastError: message,
|
|
1153
1468
|
priority: job.priority + 10,
|
|
@@ -1197,7 +1512,7 @@ export async function withBuilderBodiesSourceValues(
|
|
|
1197
1512
|
);
|
|
1198
1513
|
}
|
|
1199
1514
|
|
|
1200
|
-
async function builderBodyChangeForLocalContent(args: {
|
|
1515
|
+
export async function builderBodyChangeForLocalContent(args: {
|
|
1201
1516
|
row: Pick<ContentDatabaseSourceRecordRowDb, "sourceValuesJson">;
|
|
1202
1517
|
localContent: string | null | undefined;
|
|
1203
1518
|
}): Promise<ContentDatabaseSourceBodyChange | null> {
|
|
@@ -1221,6 +1536,23 @@ async function builderBodyChangeForLocalContent(args: {
|
|
|
1221
1536
|
stringSourceValue(sourceValues, BUILDER_CMS_BODY_SIDECARS_KEY) ?? "{}";
|
|
1222
1537
|
const localContent = args.localContent ?? "";
|
|
1223
1538
|
if (!currentHash && !currentContent && !localContent.trim()) return null;
|
|
1539
|
+
if (!currentContent?.trim() && !losslessContent?.trim()) return null;
|
|
1540
|
+
const normalizedLocalContent =
|
|
1541
|
+
normalizeBuilderBodyBaselineContent(localContent);
|
|
1542
|
+
if (
|
|
1543
|
+
normalizedLocalContent &&
|
|
1544
|
+
normalizedLocalContent ===
|
|
1545
|
+
normalizeBuilderBodyBaselineContent(currentContent)
|
|
1546
|
+
) {
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
if (
|
|
1550
|
+
normalizedLocalContent &&
|
|
1551
|
+
normalizedLocalContent ===
|
|
1552
|
+
normalizeBuilderBodyBaselineContent(losslessContent)
|
|
1553
|
+
) {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1224
1556
|
|
|
1225
1557
|
let sidecars: Record<string, string> = {};
|
|
1226
1558
|
try {
|
|
@@ -1308,6 +1640,7 @@ export function buildBuilderLocalOutboundChangeSets(args: {
|
|
|
1308
1640
|
localFieldKey: string;
|
|
1309
1641
|
sourceFieldKey: string;
|
|
1310
1642
|
sourceFieldLabel: string;
|
|
1643
|
+
propertyType?: DocumentProperty["definition"]["type"] | null;
|
|
1311
1644
|
}>;
|
|
1312
1645
|
// Row-union scoping (multi-source). Documents owned by ANOTHER source must
|
|
1313
1646
|
// never be create candidates for this one — each row belongs to exactly one
|
|
@@ -1322,6 +1655,7 @@ export function buildBuilderLocalOutboundChangeSets(args: {
|
|
|
1322
1655
|
// untagged / "Local" row falls back to the primary (allowUnsourcedCreates).
|
|
1323
1656
|
taggedSourceByDocumentId?: Map<string, string>;
|
|
1324
1657
|
bodyChangeByDocumentId?: Map<string, ContentDatabaseSourceBodyChange>;
|
|
1658
|
+
sourceImportedDocumentIds?: Set<string>;
|
|
1325
1659
|
}): ContentDatabaseSourceChangeSet[] {
|
|
1326
1660
|
if (normalizeSourceType(args.source.sourceType) !== "builder-cms") return [];
|
|
1327
1661
|
|
|
@@ -1366,13 +1700,19 @@ export function buildBuilderLocalOutboundChangeSets(args: {
|
|
|
1366
1700
|
if (!rowLocalValues.has(field.localFieldKey)) continue;
|
|
1367
1701
|
const localValue = rowLocalValues.get(field.localFieldKey);
|
|
1368
1702
|
const baseValue = rowSourceValues[field.sourceFieldKey];
|
|
1369
|
-
if (
|
|
1703
|
+
if (
|
|
1704
|
+
sameMappedSourceFieldValue(localValue, baseValue, field.propertyType)
|
|
1705
|
+
) {
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1370
1708
|
fieldChanges.push({
|
|
1371
1709
|
propertyId: field.propertyId,
|
|
1372
1710
|
propertyName: field.sourceFieldLabel,
|
|
1373
1711
|
localFieldKey: field.localFieldKey,
|
|
1374
1712
|
sourceFieldKey: field.sourceFieldKey,
|
|
1375
|
-
currentValue: (
|
|
1713
|
+
currentValue: (field.propertyType
|
|
1714
|
+
? normalizePropertyValue(field.propertyType, baseValue)
|
|
1715
|
+
: (baseValue ?? null)) as DocumentPropertyValue,
|
|
1376
1716
|
proposedValue: localValue as DocumentPropertyValue,
|
|
1377
1717
|
});
|
|
1378
1718
|
}
|
|
@@ -1459,6 +1799,7 @@ export function buildBuilderLocalOutboundChangeSets(args: {
|
|
|
1459
1799
|
const allowUnsourcedCreates = args.allowUnsourcedCreates ?? true;
|
|
1460
1800
|
for (const item of args.databaseItems) {
|
|
1461
1801
|
if (linkedDocumentIds.has(item.documentId)) continue;
|
|
1802
|
+
if (args.sourceImportedDocumentIds?.has(item.documentId)) continue;
|
|
1462
1803
|
// Owned by another collection's row identity — not this source's to create.
|
|
1463
1804
|
if (args.otherSourceDocumentIds?.has(item.documentId)) continue;
|
|
1464
1805
|
const taggedSourceId = args.taggedSourceByDocumentId?.get(
|
|
@@ -1646,87 +1987,20 @@ export async function getAllContentDatabaseSourceSnapshots(
|
|
|
1646
1987
|
return snapshots;
|
|
1647
1988
|
}
|
|
1648
1989
|
|
|
1649
|
-
async function
|
|
1650
|
-
source: ContentDatabaseSourceRowDb
|
|
1651
|
-
database: ContentDatabaseRow | ContentDatabase
|
|
1652
|
-
|
|
1990
|
+
async function readSourceSnapshotRowsOnce(args: {
|
|
1991
|
+
source: ContentDatabaseSourceRowDb;
|
|
1992
|
+
database: ContentDatabaseRow | ContentDatabase;
|
|
1993
|
+
isBuilderSource: boolean;
|
|
1994
|
+
}) {
|
|
1653
1995
|
const db = getDb();
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
executionRows,
|
|
1660
|
-
propertyDefs,
|
|
1661
|
-
] = await Promise.all([
|
|
1662
|
-
db
|
|
1663
|
-
.select()
|
|
1664
|
-
.from(schema.contentDatabaseSourceFields)
|
|
1665
|
-
.where(eq(schema.contentDatabaseSourceFields.sourceId, source.id))
|
|
1666
|
-
.orderBy(asc(schema.contentDatabaseSourceFields.createdAt)),
|
|
1667
|
-
db
|
|
1668
|
-
.select()
|
|
1669
|
-
.from(schema.contentDatabaseSourceRows)
|
|
1670
|
-
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id))
|
|
1671
|
-
.orderBy(asc(schema.contentDatabaseSourceRows.createdAt)),
|
|
1672
|
-
db
|
|
1673
|
-
.select()
|
|
1674
|
-
.from(schema.contentDatabaseSourceChangeSets)
|
|
1675
|
-
.where(eq(schema.contentDatabaseSourceChangeSets.sourceId, source.id))
|
|
1676
|
-
.orderBy(asc(schema.contentDatabaseSourceChangeSets.createdAt)),
|
|
1677
|
-
db
|
|
1678
|
-
.select()
|
|
1679
|
-
.from(schema.contentDatabaseSourceChangeReviews)
|
|
1680
|
-
.where(eq(schema.contentDatabaseSourceChangeReviews.sourceId, source.id))
|
|
1681
|
-
.orderBy(asc(schema.contentDatabaseSourceChangeReviews.createdAt)),
|
|
1682
|
-
db
|
|
1683
|
-
.select()
|
|
1684
|
-
.from(schema.contentDatabaseSourceExecutions)
|
|
1685
|
-
.where(eq(schema.contentDatabaseSourceExecutions.sourceId, source.id))
|
|
1686
|
-
.orderBy(asc(schema.contentDatabaseSourceExecutions.createdAt)),
|
|
1687
|
-
db
|
|
1688
|
-
.select({
|
|
1689
|
-
id: schema.documentPropertyDefinitions.id,
|
|
1690
|
-
name: schema.documentPropertyDefinitions.name,
|
|
1691
|
-
})
|
|
1692
|
-
.from(schema.documentPropertyDefinitions)
|
|
1693
|
-
.where(eq(schema.documentPropertyDefinitions.databaseId, database.id)),
|
|
1694
|
-
]);
|
|
1695
|
-
|
|
1696
|
-
const propertyNameById = new Map(
|
|
1697
|
-
propertyDefs.map((row) => [row.id, row.name]),
|
|
1698
|
-
);
|
|
1699
|
-
const fields = fieldRows.map((row) =>
|
|
1700
|
-
serializeSourceField(
|
|
1701
|
-
row,
|
|
1702
|
-
row.propertyId ? (propertyNameById.get(row.propertyId) ?? null) : null,
|
|
1703
|
-
),
|
|
1704
|
-
);
|
|
1705
|
-
const rows = rowRows.map(serializeSourceRowRecord);
|
|
1706
|
-
const storedChangeSets = changeRows.map(serializeSourceChangeSet);
|
|
1707
|
-
const reviewEventsByChangeSetId = new Map<
|
|
1708
|
-
string,
|
|
1709
|
-
ContentDatabaseSourceReviewEvent[]
|
|
1710
|
-
>();
|
|
1711
|
-
for (const row of reviewRows) {
|
|
1712
|
-
const events = reviewEventsByChangeSetId.get(row.changeSetId) ?? [];
|
|
1713
|
-
events.push(serializeReviewEvent(row));
|
|
1714
|
-
reviewEventsByChangeSetId.set(row.changeSetId, events);
|
|
1715
|
-
}
|
|
1716
|
-
const executionsByChangeSetId = new Map<
|
|
1717
|
-
string,
|
|
1718
|
-
ContentDatabaseSourceExecution[]
|
|
1719
|
-
>();
|
|
1720
|
-
for (const row of executionRows) {
|
|
1721
|
-
const executions = executionsByChangeSetId.get(row.changeSetId) ?? [];
|
|
1722
|
-
executions.push(serializeExecution(row));
|
|
1723
|
-
executionsByChangeSetId.set(row.changeSetId, executions);
|
|
1724
|
-
}
|
|
1725
|
-
const isBuilderSource =
|
|
1726
|
-
normalizeSourceType(source.sourceType) === "builder-cms";
|
|
1996
|
+
const rowRows = await db
|
|
1997
|
+
.select()
|
|
1998
|
+
.from(schema.contentDatabaseSourceRows)
|
|
1999
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id))
|
|
2000
|
+
.orderBy(asc(schema.contentDatabaseSourceRows.createdAt));
|
|
1727
2001
|
// For Builder sources, load ALL database items (not just synced source rows)
|
|
1728
2002
|
// so brand-new local rows (no source link) can become create_draft change-sets.
|
|
1729
|
-
const databaseItemRows = isBuilderSource
|
|
2003
|
+
const databaseItemRows = args.isBuilderSource
|
|
1730
2004
|
? await db
|
|
1731
2005
|
.select({
|
|
1732
2006
|
id: schema.contentDatabaseItems.id,
|
|
@@ -1736,8 +2010,8 @@ async function loadSourceSnapshot(
|
|
|
1736
2010
|
.from(schema.contentDatabaseItems)
|
|
1737
2011
|
.where(
|
|
1738
2012
|
and(
|
|
1739
|
-
eq(schema.contentDatabaseItems.databaseId, database.id),
|
|
1740
|
-
eq(schema.contentDatabaseItems.ownerEmail, source.ownerEmail),
|
|
2013
|
+
eq(schema.contentDatabaseItems.databaseId, args.database.id),
|
|
2014
|
+
eq(schema.contentDatabaseItems.ownerEmail, args.source.ownerEmail),
|
|
1741
2015
|
),
|
|
1742
2016
|
)
|
|
1743
2017
|
: [];
|
|
@@ -1759,18 +2033,12 @@ async function loadSourceSnapshot(
|
|
|
1759
2033
|
.where(
|
|
1760
2034
|
and(
|
|
1761
2035
|
inArray(schema.documents.id, allDocumentIds),
|
|
1762
|
-
eq(schema.documents.ownerEmail, source.ownerEmail),
|
|
2036
|
+
eq(schema.documents.ownerEmail, args.source.ownerEmail),
|
|
1763
2037
|
),
|
|
1764
2038
|
)
|
|
1765
2039
|
: [];
|
|
1766
|
-
const documentTitleById = new Map(
|
|
1767
|
-
rowDocuments.map((document) => [document.id, document.title]),
|
|
1768
|
-
);
|
|
1769
|
-
const documentContentById = new Map(
|
|
1770
|
-
rowDocuments.map((document) => [document.id, document.content]),
|
|
1771
|
-
);
|
|
1772
2040
|
const propertyValueRows =
|
|
1773
|
-
isBuilderSource && allDocumentIds.length > 0
|
|
2041
|
+
args.isBuilderSource && allDocumentIds.length > 0
|
|
1774
2042
|
? await db
|
|
1775
2043
|
.select({
|
|
1776
2044
|
documentId: schema.documentPropertyValues.documentId,
|
|
@@ -1781,10 +2049,182 @@ async function loadSourceSnapshot(
|
|
|
1781
2049
|
.where(
|
|
1782
2050
|
and(
|
|
1783
2051
|
inArray(schema.documentPropertyValues.documentId, allDocumentIds),
|
|
1784
|
-
eq(
|
|
2052
|
+
eq(
|
|
2053
|
+
schema.documentPropertyValues.ownerEmail,
|
|
2054
|
+
args.source.ownerEmail,
|
|
2055
|
+
),
|
|
1785
2056
|
),
|
|
1786
2057
|
)
|
|
1787
2058
|
: [];
|
|
2059
|
+
return {
|
|
2060
|
+
rowRows,
|
|
2061
|
+
databaseItemRows,
|
|
2062
|
+
allDocumentIds,
|
|
2063
|
+
rowDocuments,
|
|
2064
|
+
propertyValueRows,
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
async function sourceSnapshotConsistencyMarker(args: {
|
|
2069
|
+
source: ContentDatabaseSourceRowDb;
|
|
2070
|
+
database: ContentDatabaseRow | ContentDatabase;
|
|
2071
|
+
isBuilderSource: boolean;
|
|
2072
|
+
}) {
|
|
2073
|
+
const db = getDb();
|
|
2074
|
+
const [rows] = await db
|
|
2075
|
+
.select({
|
|
2076
|
+
count: sql<number>`COUNT(*)`,
|
|
2077
|
+
maxUpdatedAt: sql<
|
|
2078
|
+
string | null
|
|
2079
|
+
>`MAX(${schema.contentDatabaseSourceRows.updatedAt})`,
|
|
2080
|
+
})
|
|
2081
|
+
.from(schema.contentDatabaseSourceRows)
|
|
2082
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
|
|
2083
|
+
const [items] = args.isBuilderSource
|
|
2084
|
+
? await db
|
|
2085
|
+
.select({
|
|
2086
|
+
count: sql<number>`COUNT(*)`,
|
|
2087
|
+
maxUpdatedAt: sql<
|
|
2088
|
+
string | null
|
|
2089
|
+
>`MAX(${schema.contentDatabaseItems.updatedAt})`,
|
|
2090
|
+
})
|
|
2091
|
+
.from(schema.contentDatabaseItems)
|
|
2092
|
+
.where(
|
|
2093
|
+
and(
|
|
2094
|
+
eq(schema.contentDatabaseItems.databaseId, args.database.id),
|
|
2095
|
+
eq(schema.contentDatabaseItems.ownerEmail, args.source.ownerEmail),
|
|
2096
|
+
),
|
|
2097
|
+
)
|
|
2098
|
+
: [{ count: 0, maxUpdatedAt: null }];
|
|
2099
|
+
return {
|
|
2100
|
+
rowCount: Number(rows?.count ?? 0),
|
|
2101
|
+
rowMaxUpdatedAt: rows?.maxUpdatedAt ?? null,
|
|
2102
|
+
itemCount: Number(items?.count ?? 0),
|
|
2103
|
+
itemMaxUpdatedAt: items?.maxUpdatedAt ?? null,
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function sourceSnapshotConsistencyMarkersEqual(
|
|
2108
|
+
left: Awaited<ReturnType<typeof sourceSnapshotConsistencyMarker>>,
|
|
2109
|
+
right: Awaited<ReturnType<typeof sourceSnapshotConsistencyMarker>>,
|
|
2110
|
+
) {
|
|
2111
|
+
return (
|
|
2112
|
+
left.rowCount === right.rowCount &&
|
|
2113
|
+
left.rowMaxUpdatedAt === right.rowMaxUpdatedAt &&
|
|
2114
|
+
left.itemCount === right.itemCount &&
|
|
2115
|
+
left.itemMaxUpdatedAt === right.itemMaxUpdatedAt
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
async function loadSourceSnapshotRowsOptimistically(args: {
|
|
2120
|
+
source: ContentDatabaseSourceRowDb;
|
|
2121
|
+
database: ContentDatabaseRow | ContentDatabase;
|
|
2122
|
+
isBuilderSource: boolean;
|
|
2123
|
+
}) {
|
|
2124
|
+
let latest: Awaited<ReturnType<typeof readSourceSnapshotRowsOnce>> | null =
|
|
2125
|
+
null;
|
|
2126
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2127
|
+
const before = await sourceSnapshotConsistencyMarker(args);
|
|
2128
|
+
latest = await readSourceSnapshotRowsOnce(args);
|
|
2129
|
+
const after = await sourceSnapshotConsistencyMarker(args);
|
|
2130
|
+
if (sourceSnapshotConsistencyMarkersEqual(before, after)) return latest;
|
|
2131
|
+
}
|
|
2132
|
+
return latest ?? (await readSourceSnapshotRowsOnce(args));
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
async function loadSourceSnapshot(
|
|
2136
|
+
source: ContentDatabaseSourceRowDb,
|
|
2137
|
+
database: ContentDatabaseRow | ContentDatabase,
|
|
2138
|
+
): Promise<ContentDatabaseSource> {
|
|
2139
|
+
const db = getDb();
|
|
2140
|
+
const [fieldRows, changeRows, reviewRows, executionRows, propertyDefs] =
|
|
2141
|
+
await Promise.all([
|
|
2142
|
+
db
|
|
2143
|
+
.select()
|
|
2144
|
+
.from(schema.contentDatabaseSourceFields)
|
|
2145
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, source.id))
|
|
2146
|
+
.orderBy(asc(schema.contentDatabaseSourceFields.createdAt)),
|
|
2147
|
+
db
|
|
2148
|
+
.select()
|
|
2149
|
+
.from(schema.contentDatabaseSourceChangeSets)
|
|
2150
|
+
.where(eq(schema.contentDatabaseSourceChangeSets.sourceId, source.id))
|
|
2151
|
+
.orderBy(asc(schema.contentDatabaseSourceChangeSets.createdAt)),
|
|
2152
|
+
db
|
|
2153
|
+
.select()
|
|
2154
|
+
.from(schema.contentDatabaseSourceChangeReviews)
|
|
2155
|
+
.where(
|
|
2156
|
+
eq(schema.contentDatabaseSourceChangeReviews.sourceId, source.id),
|
|
2157
|
+
)
|
|
2158
|
+
.orderBy(asc(schema.contentDatabaseSourceChangeReviews.createdAt)),
|
|
2159
|
+
db
|
|
2160
|
+
.select()
|
|
2161
|
+
.from(schema.contentDatabaseSourceExecutions)
|
|
2162
|
+
.where(eq(schema.contentDatabaseSourceExecutions.sourceId, source.id))
|
|
2163
|
+
.orderBy(asc(schema.contentDatabaseSourceExecutions.createdAt)),
|
|
2164
|
+
db
|
|
2165
|
+
.select({
|
|
2166
|
+
id: schema.documentPropertyDefinitions.id,
|
|
2167
|
+
name: schema.documentPropertyDefinitions.name,
|
|
2168
|
+
type: schema.documentPropertyDefinitions.type,
|
|
2169
|
+
})
|
|
2170
|
+
.from(schema.documentPropertyDefinitions)
|
|
2171
|
+
.where(eq(schema.documentPropertyDefinitions.databaseId, database.id)),
|
|
2172
|
+
]);
|
|
2173
|
+
|
|
2174
|
+
const propertyNameById = new Map(
|
|
2175
|
+
propertyDefs.map((row) => [row.id, row.name]),
|
|
2176
|
+
);
|
|
2177
|
+
const propertyTypeById = new Map(
|
|
2178
|
+
propertyDefs.map((row) => [
|
|
2179
|
+
row.id,
|
|
2180
|
+
row.type as DocumentProperty["definition"]["type"],
|
|
2181
|
+
]),
|
|
2182
|
+
);
|
|
2183
|
+
const fields = fieldRows.map((row) =>
|
|
2184
|
+
serializeSourceField(
|
|
2185
|
+
row,
|
|
2186
|
+
row.propertyId ? (propertyNameById.get(row.propertyId) ?? null) : null,
|
|
2187
|
+
),
|
|
2188
|
+
);
|
|
2189
|
+
const storedChangeSets = changeRows.map(serializeSourceChangeSet);
|
|
2190
|
+
const reviewEventsByChangeSetId = new Map<
|
|
2191
|
+
string,
|
|
2192
|
+
ContentDatabaseSourceReviewEvent[]
|
|
2193
|
+
>();
|
|
2194
|
+
for (const row of reviewRows) {
|
|
2195
|
+
const events = reviewEventsByChangeSetId.get(row.changeSetId) ?? [];
|
|
2196
|
+
events.push(serializeReviewEvent(row));
|
|
2197
|
+
reviewEventsByChangeSetId.set(row.changeSetId, events);
|
|
2198
|
+
}
|
|
2199
|
+
const executionsByChangeSetId = new Map<
|
|
2200
|
+
string,
|
|
2201
|
+
ContentDatabaseSourceExecution[]
|
|
2202
|
+
>();
|
|
2203
|
+
for (const row of executionRows) {
|
|
2204
|
+
const executions = executionsByChangeSetId.get(row.changeSetId) ?? [];
|
|
2205
|
+
executions.push(serializeExecution(row));
|
|
2206
|
+
executionsByChangeSetId.set(row.changeSetId, executions);
|
|
2207
|
+
}
|
|
2208
|
+
const isBuilderSource =
|
|
2209
|
+
normalizeSourceType(source.sourceType) === "builder-cms";
|
|
2210
|
+
const {
|
|
2211
|
+
rowRows,
|
|
2212
|
+
databaseItemRows,
|
|
2213
|
+
allDocumentIds,
|
|
2214
|
+
rowDocuments,
|
|
2215
|
+
propertyValueRows,
|
|
2216
|
+
} = await loadSourceSnapshotRowsOptimistically({
|
|
2217
|
+
source,
|
|
2218
|
+
database,
|
|
2219
|
+
isBuilderSource,
|
|
2220
|
+
});
|
|
2221
|
+
const rows = rowRows.map(serializeSourceRowRecord);
|
|
2222
|
+
const documentTitleById = new Map(
|
|
2223
|
+
rowDocuments.map((document) => [document.id, document.title]),
|
|
2224
|
+
);
|
|
2225
|
+
const documentContentById = new Map(
|
|
2226
|
+
rowDocuments.map((document) => [document.id, document.content]),
|
|
2227
|
+
);
|
|
1788
2228
|
const localValuesByDocument = new Map<string, Map<string, unknown>>();
|
|
1789
2229
|
for (const valueRow of propertyValueRows) {
|
|
1790
2230
|
let byField = localValuesByDocument.get(valueRow.documentId);
|
|
@@ -1807,6 +2247,9 @@ async function loadSourceSnapshot(
|
|
|
1807
2247
|
localFieldKey: row.localFieldKey,
|
|
1808
2248
|
sourceFieldKey: row.sourceFieldKey,
|
|
1809
2249
|
sourceFieldLabel: row.sourceFieldLabel,
|
|
2250
|
+
propertyType: row.propertyId
|
|
2251
|
+
? (propertyTypeById.get(row.propertyId) ?? null)
|
|
2252
|
+
: null,
|
|
1810
2253
|
}));
|
|
1811
2254
|
// Row-union ownership scoping (Builder only). Determine which documents belong
|
|
1812
2255
|
// to OTHER sources and whether this source is the primary (oldest), so the
|
|
@@ -1888,9 +2331,8 @@ async function loadSourceSnapshot(
|
|
|
1888
2331
|
await Promise.all(
|
|
1889
2332
|
allDocumentIds.map(async (documentId) => {
|
|
1890
2333
|
if (!hydratedDocumentIds.has(documentId)) return;
|
|
1891
|
-
const row = sourceRowByDocumentId.get(documentId)
|
|
1892
|
-
|
|
1893
|
-
};
|
|
2334
|
+
const row = sourceRowByDocumentId.get(documentId);
|
|
2335
|
+
if (!row) return;
|
|
1894
2336
|
const bodyChange = await builderBodyChangeForLocalContent({
|
|
1895
2337
|
row,
|
|
1896
2338
|
localContent: documentContentById.get(documentId),
|
|
@@ -1915,6 +2357,11 @@ async function loadSourceSnapshot(
|
|
|
1915
2357
|
allowUnsourcedCreates: isPrimarySource,
|
|
1916
2358
|
taggedSourceByDocumentId,
|
|
1917
2359
|
bodyChangeByDocumentId,
|
|
2360
|
+
sourceImportedDocumentIds: new Set(
|
|
2361
|
+
rowRows
|
|
2362
|
+
.filter((row) => row.provenance === "Builder CMS read adapter")
|
|
2363
|
+
.map((row) => row.documentId),
|
|
2364
|
+
),
|
|
1918
2365
|
});
|
|
1919
2366
|
const rowByDocumentId = new Map(rowRows.map((row) => [row.documentId, row]));
|
|
1920
2367
|
const changeSets = [...storedChangeSets, ...localOutboundChangeSets].map(
|
|
@@ -1990,6 +2437,32 @@ async function loadSourceSnapshot(
|
|
|
1990
2437
|
typeof metadata.lastReadMatchedRowCount === "number"
|
|
1991
2438
|
? metadata.lastReadMatchedRowCount
|
|
1992
2439
|
: undefined,
|
|
2440
|
+
lastReadLimit:
|
|
2441
|
+
typeof metadata.lastReadLimit === "number"
|
|
2442
|
+
? metadata.lastReadLimit
|
|
2443
|
+
: undefined,
|
|
2444
|
+
lastReadFetchedEntryCount:
|
|
2445
|
+
typeof metadata.lastReadFetchedEntryCount === "number"
|
|
2446
|
+
? metadata.lastReadFetchedEntryCount
|
|
2447
|
+
: undefined,
|
|
2448
|
+
lastReadPartial:
|
|
2449
|
+
typeof metadata.lastReadPartial === "boolean"
|
|
2450
|
+
? metadata.lastReadPartial
|
|
2451
|
+
: undefined,
|
|
2452
|
+
lastReadHasMore:
|
|
2453
|
+
typeof metadata.lastReadHasMore === "boolean"
|
|
2454
|
+
? metadata.lastReadHasMore
|
|
2455
|
+
: undefined,
|
|
2456
|
+
lastReadNextOffset:
|
|
2457
|
+
typeof metadata.lastReadNextOffset === "number"
|
|
2458
|
+
? metadata.lastReadNextOffset
|
|
2459
|
+
: undefined,
|
|
2460
|
+
sourceFetchState:
|
|
2461
|
+
metadata.sourceFetchState === "idle" ||
|
|
2462
|
+
metadata.sourceFetchState === "fetching" ||
|
|
2463
|
+
metadata.sourceFetchState === "error"
|
|
2464
|
+
? metadata.sourceFetchState
|
|
2465
|
+
: undefined,
|
|
1993
2466
|
allowDraftWrites: metadata.allowDraftWrites === true,
|
|
1994
2467
|
allowPublishWrites: metadata.allowPublishWrites === true,
|
|
1995
2468
|
allowedWriteModes: Array.isArray(metadata.allowedWriteModes)
|
|
@@ -2011,7 +2484,10 @@ async function sourceBodyHydrationSummary(args: {
|
|
|
2011
2484
|
databaseId: string;
|
|
2012
2485
|
}): Promise<ContentDatabaseBodyHydrationSummary> {
|
|
2013
2486
|
const rows = await getDb()
|
|
2014
|
-
.select({
|
|
2487
|
+
.select({
|
|
2488
|
+
status: schema.contentDatabaseItems.bodyHydrationStatus,
|
|
2489
|
+
queueId: schema.contentDatabaseBodyHydrationQueue.id,
|
|
2490
|
+
})
|
|
2015
2491
|
.from(schema.contentDatabaseItems)
|
|
2016
2492
|
.innerJoin(
|
|
2017
2493
|
schema.contentDatabaseSourceRows,
|
|
@@ -2020,6 +2496,16 @@ async function sourceBodyHydrationSummary(args: {
|
|
|
2020
2496
|
schema.contentDatabaseItems.id,
|
|
2021
2497
|
),
|
|
2022
2498
|
)
|
|
2499
|
+
.leftJoin(
|
|
2500
|
+
schema.contentDatabaseBodyHydrationQueue,
|
|
2501
|
+
and(
|
|
2502
|
+
eq(
|
|
2503
|
+
schema.contentDatabaseBodyHydrationQueue.databaseItemId,
|
|
2504
|
+
schema.contentDatabaseItems.id,
|
|
2505
|
+
),
|
|
2506
|
+
eq(schema.contentDatabaseBodyHydrationQueue.sourceId, args.sourceId),
|
|
2507
|
+
),
|
|
2508
|
+
)
|
|
2023
2509
|
.where(
|
|
2024
2510
|
and(
|
|
2025
2511
|
eq(schema.contentDatabaseItems.databaseId, args.databaseId),
|
|
@@ -2034,8 +2520,9 @@ async function sourceBodyHydrationSummary(args: {
|
|
|
2034
2520
|
total: rows.length,
|
|
2035
2521
|
};
|
|
2036
2522
|
for (const row of rows) {
|
|
2037
|
-
if (row.status === "pending"
|
|
2038
|
-
|
|
2523
|
+
if (row.status === "pending" || (!row.status && row.queueId)) {
|
|
2524
|
+
summary.pending += 1;
|
|
2525
|
+
} else if (row.status === "hydrating") summary.hydrating += 1;
|
|
2039
2526
|
else if (row.status === "error") summary.error += 1;
|
|
2040
2527
|
else summary.hydrated += 1;
|
|
2041
2528
|
}
|
|
@@ -2117,6 +2604,9 @@ export function serializeBuilderCmsSourceReadMetadataRecord(args: {
|
|
|
2117
2604
|
readState: BuilderCmsReadState;
|
|
2118
2605
|
entryCount: number;
|
|
2119
2606
|
matchedRowCount: number;
|
|
2607
|
+
progress?: BuilderCmsReadProgress;
|
|
2608
|
+
sourceFetchState?: "idle" | "fetching" | "error";
|
|
2609
|
+
activeReadSourceRowIds?: string[];
|
|
2120
2610
|
}) {
|
|
2121
2611
|
return JSON.stringify({
|
|
2122
2612
|
...builderCmsSourceMetadata(args.sourceTable),
|
|
@@ -2124,6 +2614,19 @@ export function serializeBuilderCmsSourceReadMetadataRecord(args: {
|
|
|
2124
2614
|
liveReadConfigured: args.readState === "live",
|
|
2125
2615
|
lastReadEntryCount: args.entryCount,
|
|
2126
2616
|
lastReadMatchedRowCount: args.matchedRowCount,
|
|
2617
|
+
lastReadLimit: args.progress?.requestedLimit,
|
|
2618
|
+
lastReadFetchedEntryCount: args.progress?.fetchedEntryCount,
|
|
2619
|
+
lastReadPartial: args.progress?.partial,
|
|
2620
|
+
lastReadHasMore: args.progress?.hasMore,
|
|
2621
|
+
lastReadNextOffset: args.progress?.nextOffset,
|
|
2622
|
+
activeReadSourceRowIds: args.activeReadSourceRowIds,
|
|
2623
|
+
sourceFetchState:
|
|
2624
|
+
args.sourceFetchState ??
|
|
2625
|
+
(args.progress?.partial
|
|
2626
|
+
? "fetching"
|
|
2627
|
+
: args.readState === "error"
|
|
2628
|
+
? "error"
|
|
2629
|
+
: "idle"),
|
|
2127
2630
|
});
|
|
2128
2631
|
}
|
|
2129
2632
|
|
|
@@ -2195,6 +2698,7 @@ export async function seedMockSourceFields(args: {
|
|
|
2195
2698
|
properties: DocumentProperty[];
|
|
2196
2699
|
builderModelFields?: BuilderCmsModelFieldSummary[];
|
|
2197
2700
|
builderSampleEntries?: BuilderCmsSourceEntry[];
|
|
2701
|
+
existingFields?: ContentDatabaseSourceFieldRowDb[];
|
|
2198
2702
|
now: string;
|
|
2199
2703
|
}) {
|
|
2200
2704
|
const db = getDb();
|
|
@@ -2404,7 +2908,30 @@ export async function seedMockSourceFields(args: {
|
|
|
2404
2908
|
}
|
|
2405
2909
|
}
|
|
2406
2910
|
|
|
2407
|
-
|
|
2911
|
+
const existingFieldBySourceKey = new Map(
|
|
2912
|
+
(args.existingFields ?? []).map((field) => [
|
|
2913
|
+
field.sourceFieldKey.trim().toLowerCase(),
|
|
2914
|
+
field,
|
|
2915
|
+
]),
|
|
2916
|
+
);
|
|
2917
|
+
const mergedRows = rows.map((row) => {
|
|
2918
|
+
const existing = existingFieldBySourceKey.get(
|
|
2919
|
+
row.sourceFieldKey.trim().toLowerCase(),
|
|
2920
|
+
);
|
|
2921
|
+
if (!existing) return row;
|
|
2922
|
+
return {
|
|
2923
|
+
...row,
|
|
2924
|
+
id: existing.id,
|
|
2925
|
+
propertyId: existing.propertyId ?? row.propertyId,
|
|
2926
|
+
localFieldKey: existing.propertyId
|
|
2927
|
+
? existing.localFieldKey
|
|
2928
|
+
: row.localFieldKey,
|
|
2929
|
+
mappingType: existing.propertyId ? existing.mappingType : row.mappingType,
|
|
2930
|
+
createdAt: existing.createdAt,
|
|
2931
|
+
};
|
|
2932
|
+
});
|
|
2933
|
+
|
|
2934
|
+
await db.insert(schema.contentDatabaseSourceFields).values(mergedRows);
|
|
2408
2935
|
}
|
|
2409
2936
|
|
|
2410
2937
|
export async function seedMockSourceRows(args: {
|
|
@@ -2491,7 +3018,12 @@ export function sourceValuesForSeededSourceRow(args: {
|
|
|
2491
3018
|
const existingSourceValues = parseObject<
|
|
2492
3019
|
Record<string, DocumentPropertyValue>
|
|
2493
3020
|
>(args.existingSourceValuesJson);
|
|
2494
|
-
if (args.builderEntry?.sourceValues)
|
|
3021
|
+
if (args.builderEntry?.sourceValues) {
|
|
3022
|
+
return builderSourceValuesWithPreservedBodyBaseline({
|
|
3023
|
+
incoming: args.builderEntry.sourceValues,
|
|
3024
|
+
existing: existingSourceValues,
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
2495
3027
|
if (existingSourceValues) return existingSourceValues;
|
|
2496
3028
|
if (args.sourceType !== "builder-cms") return {};
|
|
2497
3029
|
return buildBuilderCmsFixtureEntry({
|
|
@@ -2501,6 +3033,135 @@ export function sourceValuesForSeededSourceRow(args: {
|
|
|
2501
3033
|
}).sourceValues;
|
|
2502
3034
|
}
|
|
2503
3035
|
|
|
3036
|
+
function builderSourceValuesWithPreservedBodyBaseline(args: {
|
|
3037
|
+
incoming: Record<string, DocumentPropertyValue>;
|
|
3038
|
+
existing?: Record<string, DocumentPropertyValue> | null;
|
|
3039
|
+
}) {
|
|
3040
|
+
const existing = args.existing;
|
|
3041
|
+
if (!existing) return args.incoming;
|
|
3042
|
+
const incomingContent = stringSourceValue(
|
|
3043
|
+
args.incoming,
|
|
3044
|
+
BUILDER_CMS_BODY_CONTENT_KEY,
|
|
3045
|
+
);
|
|
3046
|
+
const existingContent = stringSourceValue(
|
|
3047
|
+
existing,
|
|
3048
|
+
BUILDER_CMS_BODY_CONTENT_KEY,
|
|
3049
|
+
);
|
|
3050
|
+
const incomingHash = stringSourceValue(
|
|
3051
|
+
args.incoming,
|
|
3052
|
+
BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
|
|
3053
|
+
);
|
|
3054
|
+
const existingHash = stringSourceValue(
|
|
3055
|
+
existing,
|
|
3056
|
+
BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
|
|
3057
|
+
);
|
|
3058
|
+
if (
|
|
3059
|
+
incomingContent?.trim() ||
|
|
3060
|
+
!existingContent?.trim() ||
|
|
3061
|
+
(incomingHash && existingHash && incomingHash !== existingHash)
|
|
3062
|
+
) {
|
|
3063
|
+
return args.incoming;
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
const next = { ...args.incoming };
|
|
3067
|
+
for (const key of [
|
|
3068
|
+
BUILDER_CMS_BODY_CONTENT_KEY,
|
|
3069
|
+
BUILDER_CMS_BODY_LOSSLESS_CONTENT_KEY,
|
|
3070
|
+
BUILDER_CMS_BODY_READABLE_MAP_KEY,
|
|
3071
|
+
BUILDER_CMS_BODY_SIDECARS_KEY,
|
|
3072
|
+
BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
|
|
3073
|
+
]) {
|
|
3074
|
+
if (next[key] === undefined && existing[key] !== undefined) {
|
|
3075
|
+
next[key] = existing[key];
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
return next;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
async function materializeSourceFieldPropertyValues(args: {
|
|
3082
|
+
database: ContentDatabaseRow;
|
|
3083
|
+
sourceId: string;
|
|
3084
|
+
fields: ContentDatabaseSourceFieldRowDb[];
|
|
3085
|
+
documentIds?: string[];
|
|
3086
|
+
now: string;
|
|
3087
|
+
}) {
|
|
3088
|
+
const boundFields = args.fields.filter((field) => field.propertyId);
|
|
3089
|
+
if (boundFields.length === 0) return;
|
|
3090
|
+
const db = getDb();
|
|
3091
|
+
const documentIdSet =
|
|
3092
|
+
args.documentIds && args.documentIds.length > 0
|
|
3093
|
+
? new Set(args.documentIds)
|
|
3094
|
+
: null;
|
|
3095
|
+
const propertyIds = Array.from(
|
|
3096
|
+
new Set(boundFields.map((field) => field.propertyId!)),
|
|
3097
|
+
);
|
|
3098
|
+
const definitions = await db
|
|
3099
|
+
.select()
|
|
3100
|
+
.from(schema.documentPropertyDefinitions)
|
|
3101
|
+
.where(inArray(schema.documentPropertyDefinitions.id, propertyIds));
|
|
3102
|
+
const definitionById = new Map(
|
|
3103
|
+
definitions.map((definition) => [definition.id, definition]),
|
|
3104
|
+
);
|
|
3105
|
+
const sourceRows = await db
|
|
3106
|
+
.select()
|
|
3107
|
+
.from(schema.contentDatabaseSourceRows)
|
|
3108
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.sourceId));
|
|
3109
|
+
const scopedRows = documentIdSet
|
|
3110
|
+
? sourceRows.filter((row) => documentIdSet.has(row.documentId))
|
|
3111
|
+
: sourceRows;
|
|
3112
|
+
if (scopedRows.length === 0) return;
|
|
3113
|
+
|
|
3114
|
+
const existingValues = await db
|
|
3115
|
+
.select()
|
|
3116
|
+
.from(schema.documentPropertyValues)
|
|
3117
|
+
.where(inArray(schema.documentPropertyValues.propertyId, propertyIds));
|
|
3118
|
+
const existingByDocumentAndProperty = new Map(
|
|
3119
|
+
existingValues.map((value) => [
|
|
3120
|
+
`${value.documentId}\0${value.propertyId}`,
|
|
3121
|
+
value,
|
|
3122
|
+
]),
|
|
3123
|
+
);
|
|
3124
|
+
|
|
3125
|
+
for (const row of scopedRows) {
|
|
3126
|
+
const sourceValues =
|
|
3127
|
+
parseObject<Record<string, DocumentPropertyValue>>(
|
|
3128
|
+
row.sourceValuesJson,
|
|
3129
|
+
) ?? {};
|
|
3130
|
+
for (const field of boundFields) {
|
|
3131
|
+
const propertyId = field.propertyId!;
|
|
3132
|
+
const definition = definitionById.get(propertyId);
|
|
3133
|
+
if (!definition) continue;
|
|
3134
|
+
const normalized = normalizePropertyValue(
|
|
3135
|
+
definition.type as DocumentProperty["definition"]["type"],
|
|
3136
|
+
sourceValues[field.sourceFieldKey],
|
|
3137
|
+
);
|
|
3138
|
+
if (normalized === null) continue;
|
|
3139
|
+
const valueJson = serializePropertyValue(normalized);
|
|
3140
|
+
const key = `${row.documentId}\0${propertyId}`;
|
|
3141
|
+
const existing = existingByDocumentAndProperty.get(key);
|
|
3142
|
+
if (existing) {
|
|
3143
|
+
if (existing.valueJson === valueJson) continue;
|
|
3144
|
+
await db
|
|
3145
|
+
.update(schema.documentPropertyValues)
|
|
3146
|
+
.set({ valueJson, updatedAt: args.now })
|
|
3147
|
+
.where(eq(schema.documentPropertyValues.id, existing.id));
|
|
3148
|
+
} else {
|
|
3149
|
+
const value = {
|
|
3150
|
+
id: nanoid(),
|
|
3151
|
+
ownerEmail: args.database.ownerEmail,
|
|
3152
|
+
documentId: row.documentId,
|
|
3153
|
+
propertyId,
|
|
3154
|
+
valueJson,
|
|
3155
|
+
createdAt: args.now,
|
|
3156
|
+
updatedAt: args.now,
|
|
3157
|
+
};
|
|
3158
|
+
await db.insert(schema.documentPropertyValues).values(value);
|
|
3159
|
+
existingByDocumentAndProperty.set(key, value);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
2504
3165
|
function openChangeSetKey(row: ContentDatabaseSourceChangeSetRowDb) {
|
|
2505
3166
|
const fields = parseArray<ContentDatabaseSourceFieldChange>(
|
|
2506
3167
|
row.fieldChangesJson,
|
|
@@ -2855,8 +3516,14 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
|
|
|
2855
3516
|
// per-source re-import idempotency is still handled by
|
|
2856
3517
|
// builderCmsEntryAlreadyRepresented (existingSourceRows).
|
|
2857
3518
|
skipTitleDedup?: boolean;
|
|
2858
|
-
}) {
|
|
2859
|
-
|
|
3519
|
+
}): Promise<{
|
|
3520
|
+
imported: number;
|
|
3521
|
+
importedEntriesByDocumentId: Map<string, BuilderCmsSourceEntry>;
|
|
3522
|
+
}> {
|
|
3523
|
+
const importedEntriesByDocumentId = new Map<string, BuilderCmsSourceEntry>();
|
|
3524
|
+
if (args.entries.length === 0) {
|
|
3525
|
+
return { imported: 0, importedEntriesByDocumentId };
|
|
3526
|
+
}
|
|
2860
3527
|
const db = getDb();
|
|
2861
3528
|
const [databaseDocument] = await db
|
|
2862
3529
|
.select()
|
|
@@ -2940,36 +3607,73 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
|
|
|
2940
3607
|
updatedAt: args.now,
|
|
2941
3608
|
});
|
|
2942
3609
|
imported += 1;
|
|
3610
|
+
importedEntriesByDocumentId.set(documentId, entry);
|
|
2943
3611
|
}
|
|
2944
3612
|
|
|
2945
|
-
return imported;
|
|
3613
|
+
return { imported, importedEntriesByDocumentId };
|
|
2946
3614
|
}
|
|
2947
3615
|
|
|
2948
3616
|
export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
2949
3617
|
database: ContentDatabaseRow;
|
|
2950
3618
|
source: ContentDatabaseSourceRowDb;
|
|
2951
3619
|
now: string;
|
|
3620
|
+
runFullRefresh?: boolean;
|
|
2952
3621
|
}) {
|
|
2953
3622
|
let { properties, response } = await sourceSetupPayload(args.database.id);
|
|
2954
3623
|
const db = getDb();
|
|
3624
|
+
const sourceMetadata =
|
|
3625
|
+
parseObject<SourceMetadataRecord>(args.source.metadataJson) ?? {};
|
|
3626
|
+
const activeReadSourceRowIds =
|
|
3627
|
+
Array.isArray(sourceMetadata.activeReadSourceRowIds) &&
|
|
3628
|
+
sourceMetadata.activeReadSourceRowIds.every((id) => typeof id === "string")
|
|
3629
|
+
? sourceMetadata.activeReadSourceRowIds
|
|
3630
|
+
: [];
|
|
3631
|
+
const continueOffset =
|
|
3632
|
+
!args.runFullRefresh &&
|
|
3633
|
+
sourceMetadata.sourceFetchState === "fetching" &&
|
|
3634
|
+
sourceMetadata.lastReadHasMore === true &&
|
|
3635
|
+
activeReadSourceRowIds.length > 0 &&
|
|
3636
|
+
typeof sourceMetadata.lastReadNextOffset === "number" &&
|
|
3637
|
+
sourceMetadata.lastReadNextOffset > 0
|
|
3638
|
+
? sourceMetadata.lastReadNextOffset
|
|
3639
|
+
: 0;
|
|
2955
3640
|
const builderRead = await readBuilderCmsContentEntries({
|
|
2956
3641
|
model: args.source.sourceTable,
|
|
3642
|
+
maxPages: args.runFullRefresh
|
|
3643
|
+
? undefined
|
|
3644
|
+
: BUILDER_CMS_REFRESH_INITIAL_PAGES,
|
|
3645
|
+
offset: continueOffset,
|
|
2957
3646
|
});
|
|
3647
|
+
const incrementalRead =
|
|
3648
|
+
builderRead.state === "live" &&
|
|
3649
|
+
!args.runFullRefresh &&
|
|
3650
|
+
(builderRead.progress?.partial === true ||
|
|
3651
|
+
(builderRead.progress?.startOffset ?? 0) > 0);
|
|
2958
3652
|
const builderEntries =
|
|
2959
3653
|
builderRead.state === "live" ? builderRead.entries : [];
|
|
2960
3654
|
let existingRows = await db
|
|
2961
3655
|
.select()
|
|
2962
3656
|
.from(schema.contentDatabaseSourceRows)
|
|
2963
3657
|
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
|
|
3658
|
+
await enqueueEmptyHydratedBuilderBodiesFromStoredRows({
|
|
3659
|
+
source: args.source,
|
|
3660
|
+
now: args.now,
|
|
3661
|
+
});
|
|
3662
|
+
let importedEntriesByDocumentId = new Map<string, BuilderCmsSourceEntry>();
|
|
3663
|
+
const existingFields = await db
|
|
3664
|
+
.select()
|
|
3665
|
+
.from(schema.contentDatabaseSourceFields)
|
|
3666
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
|
|
2964
3667
|
if (builderRead.state === "live") {
|
|
2965
|
-
const
|
|
3668
|
+
const importResult = await importBuilderCmsEntriesAsDatabaseItems({
|
|
2966
3669
|
database: args.database,
|
|
2967
3670
|
entries: builderEntries,
|
|
2968
3671
|
now: args.now,
|
|
2969
3672
|
sourceTable: args.source.sourceTable,
|
|
2970
3673
|
existingSourceRows: existingRows,
|
|
2971
3674
|
});
|
|
2972
|
-
|
|
3675
|
+
importedEntriesByDocumentId = importResult.importedEntriesByDocumentId;
|
|
3676
|
+
if (importResult.imported > 0) {
|
|
2973
3677
|
({ properties, response } = await sourceSetupPayload(args.database.id));
|
|
2974
3678
|
existingRows = await db
|
|
2975
3679
|
.select()
|
|
@@ -2990,6 +3694,9 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
2990
3694
|
existingRows,
|
|
2991
3695
|
})
|
|
2992
3696
|
: new Map<string, BuilderCmsSourceEntry>();
|
|
3697
|
+
for (const [documentId, entry] of importedEntriesByDocumentId) {
|
|
3698
|
+
builderEntriesByDocumentId.set(documentId, entry);
|
|
3699
|
+
}
|
|
2993
3700
|
const existingBuilderRows = new Map<string, ExistingBuilderSourceRowIdentity>(
|
|
2994
3701
|
existingRows.map((row) => [
|
|
2995
3702
|
row.documentId,
|
|
@@ -3003,6 +3710,113 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
3003
3710
|
},
|
|
3004
3711
|
]),
|
|
3005
3712
|
);
|
|
3713
|
+
|
|
3714
|
+
if (incrementalRead) {
|
|
3715
|
+
const currentSourceRowIds = builderEntries.map((entry) => entry.id);
|
|
3716
|
+
const nextActiveReadSourceRowIds = Array.from(
|
|
3717
|
+
new Set(
|
|
3718
|
+
continueOffset > 0
|
|
3719
|
+
? [...activeReadSourceRowIds, ...currentSourceRowIds]
|
|
3720
|
+
: currentSourceRowIds,
|
|
3721
|
+
),
|
|
3722
|
+
);
|
|
3723
|
+
const hasMore = builderRead.progress?.hasMore === true;
|
|
3724
|
+
|
|
3725
|
+
await db
|
|
3726
|
+
.delete(schema.contentDatabaseSourceFields)
|
|
3727
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
|
|
3728
|
+
await seedMockSourceFields({
|
|
3729
|
+
sourceId: args.source.id,
|
|
3730
|
+
ownerEmail: args.database.ownerEmail,
|
|
3731
|
+
sourceType: "builder-cms",
|
|
3732
|
+
properties,
|
|
3733
|
+
builderModelFields,
|
|
3734
|
+
builderSampleEntries: builderEntries,
|
|
3735
|
+
existingFields,
|
|
3736
|
+
now: args.now,
|
|
3737
|
+
});
|
|
3738
|
+
|
|
3739
|
+
const itemsToLink = response.items.filter((item) =>
|
|
3740
|
+
builderEntriesByDocumentId.has(item.document.id),
|
|
3741
|
+
);
|
|
3742
|
+
const fetchedDocumentIds = itemsToLink.map((item) => item.document.id);
|
|
3743
|
+
if (fetchedDocumentIds.length > 0) {
|
|
3744
|
+
await db
|
|
3745
|
+
.delete(schema.contentDatabaseSourceRows)
|
|
3746
|
+
.where(
|
|
3747
|
+
and(
|
|
3748
|
+
eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
|
|
3749
|
+
inArray(
|
|
3750
|
+
schema.contentDatabaseSourceRows.documentId,
|
|
3751
|
+
fetchedDocumentIds,
|
|
3752
|
+
),
|
|
3753
|
+
),
|
|
3754
|
+
);
|
|
3755
|
+
await seedMockSourceRows({
|
|
3756
|
+
sourceId: args.source.id,
|
|
3757
|
+
ownerEmail: args.database.ownerEmail,
|
|
3758
|
+
sourceType: "builder-cms",
|
|
3759
|
+
sourceTable: args.source.sourceTable,
|
|
3760
|
+
items: itemsToLink,
|
|
3761
|
+
now: args.now,
|
|
3762
|
+
existingBuilderRows,
|
|
3763
|
+
builderEntriesByDocumentId,
|
|
3764
|
+
});
|
|
3765
|
+
const refreshedFields = await db
|
|
3766
|
+
.select()
|
|
3767
|
+
.from(schema.contentDatabaseSourceFields)
|
|
3768
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
|
|
3769
|
+
await materializeSourceFieldPropertyValues({
|
|
3770
|
+
database: args.database,
|
|
3771
|
+
sourceId: args.source.id,
|
|
3772
|
+
fields: refreshedFields,
|
|
3773
|
+
documentIds: fetchedDocumentIds,
|
|
3774
|
+
now: args.now,
|
|
3775
|
+
});
|
|
3776
|
+
await enqueueBuilderBodyHydrationForItems({
|
|
3777
|
+
sourceId: args.source.id,
|
|
3778
|
+
ownerEmail: args.database.ownerEmail,
|
|
3779
|
+
orgId: args.database.orgId,
|
|
3780
|
+
sourceTable: args.source.sourceTable,
|
|
3781
|
+
items: itemsToLink,
|
|
3782
|
+
builderEntriesByDocumentId,
|
|
3783
|
+
now: args.now,
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
if (!hasMore && nextActiveReadSourceRowIds.length > 0) {
|
|
3787
|
+
await db
|
|
3788
|
+
.delete(schema.contentDatabaseSourceRows)
|
|
3789
|
+
.where(
|
|
3790
|
+
and(
|
|
3791
|
+
eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
|
|
3792
|
+
notInArray(
|
|
3793
|
+
schema.contentDatabaseSourceRows.sourceRowId,
|
|
3794
|
+
nextActiveReadSourceRowIds,
|
|
3795
|
+
),
|
|
3796
|
+
),
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
await updateBuilderCmsSourceReadMetadata({
|
|
3801
|
+
sourceId: args.source.id,
|
|
3802
|
+
sourceTable: args.source.sourceTable,
|
|
3803
|
+
readState: builderRead.state,
|
|
3804
|
+
entryCount: builderRead.entries.length,
|
|
3805
|
+
matchedRowCount: builderEntriesByDocumentId.size,
|
|
3806
|
+
fetchedAt: builderRead.fetchedAt,
|
|
3807
|
+
now: args.now,
|
|
3808
|
+
message: builderRead.message,
|
|
3809
|
+
progress: {
|
|
3810
|
+
...builderRead.progress,
|
|
3811
|
+
partial: hasMore,
|
|
3812
|
+
},
|
|
3813
|
+
sourceFetchState: hasMore ? "fetching" : "idle",
|
|
3814
|
+
syncState: hasMore ? "refreshing" : "idle",
|
|
3815
|
+
activeReadSourceRowIds: hasMore ? nextActiveReadSourceRowIds : undefined,
|
|
3816
|
+
});
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3006
3820
|
await db
|
|
3007
3821
|
.delete(schema.contentDatabaseSourceFields)
|
|
3008
3822
|
.where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
|
|
@@ -3017,6 +3831,7 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
3017
3831
|
properties,
|
|
3018
3832
|
builderModelFields,
|
|
3019
3833
|
builderSampleEntries: builderEntries,
|
|
3834
|
+
existingFields,
|
|
3020
3835
|
now: args.now,
|
|
3021
3836
|
});
|
|
3022
3837
|
// Row-union: a resync must only (re)link items that BELONG to this source —
|
|
@@ -3051,6 +3866,17 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
3051
3866
|
existingBuilderRows,
|
|
3052
3867
|
builderEntriesByDocumentId,
|
|
3053
3868
|
});
|
|
3869
|
+
const refreshedFields = await db
|
|
3870
|
+
.select()
|
|
3871
|
+
.from(schema.contentDatabaseSourceFields)
|
|
3872
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
|
|
3873
|
+
await materializeSourceFieldPropertyValues({
|
|
3874
|
+
database: args.database,
|
|
3875
|
+
sourceId: args.source.id,
|
|
3876
|
+
fields: refreshedFields,
|
|
3877
|
+
documentIds: itemsToLink.map((item) => item.document.id),
|
|
3878
|
+
now: args.now,
|
|
3879
|
+
});
|
|
3054
3880
|
if (builderRead.state === "live") {
|
|
3055
3881
|
await enqueueBuilderBodyHydrationForItems({
|
|
3056
3882
|
sourceId: args.source.id,
|
|
@@ -3090,6 +3916,9 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
|
|
|
3090
3916
|
fetchedAt: builderRead.fetchedAt,
|
|
3091
3917
|
now: args.now,
|
|
3092
3918
|
message: builderRead.message,
|
|
3919
|
+
progress: builderRead.progress,
|
|
3920
|
+
sourceFetchState: builderRead.state === "error" ? "error" : "idle",
|
|
3921
|
+
activeReadSourceRowIds: undefined,
|
|
3093
3922
|
syncState: "idle",
|
|
3094
3923
|
});
|
|
3095
3924
|
}
|
|
@@ -3415,6 +4244,9 @@ export async function updateBuilderCmsSourceReadMetadata(args: {
|
|
|
3415
4244
|
fetchedAt: string;
|
|
3416
4245
|
now: string;
|
|
3417
4246
|
message: string | null;
|
|
4247
|
+
progress?: BuilderCmsReadProgress;
|
|
4248
|
+
sourceFetchState?: "idle" | "fetching" | "error";
|
|
4249
|
+
activeReadSourceRowIds?: string[];
|
|
3418
4250
|
syncState?: ContentDatabaseSourceSyncState;
|
|
3419
4251
|
}) {
|
|
3420
4252
|
const db = getDb();
|
|
@@ -3436,13 +4268,19 @@ export async function updateBuilderCmsSourceReadMetadata(args: {
|
|
|
3436
4268
|
readState: args.readState,
|
|
3437
4269
|
entryCount: args.entryCount,
|
|
3438
4270
|
matchedRowCount: args.matchedRowCount,
|
|
4271
|
+
progress: args.progress,
|
|
4272
|
+
sourceFetchState: args.sourceFetchState,
|
|
4273
|
+
activeReadSourceRowIds: args.activeReadSourceRowIds,
|
|
3439
4274
|
}),
|
|
3440
4275
|
});
|
|
3441
4276
|
await db
|
|
3442
4277
|
.update(schema.contentDatabaseSources)
|
|
3443
4278
|
.set({
|
|
3444
4279
|
syncState: args.syncState ?? "linked",
|
|
3445
|
-
freshness:
|
|
4280
|
+
freshness:
|
|
4281
|
+
args.readState === "error" || args.progress?.partial
|
|
4282
|
+
? "stale"
|
|
4283
|
+
: "fresh",
|
|
3446
4284
|
capabilitiesJson: nextJson.capabilitiesJson,
|
|
3447
4285
|
metadataJson: nextJson.metadataJson,
|
|
3448
4286
|
lastRefreshedAt: args.now,
|