@elytracms/studio 0.0.93 → 0.0.95
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/convex/analytics-cost.test.ts +817 -0
- package/convex/asset-alt-migration.test.ts +2 -2
- package/convex/asset-meta-patch.test.ts +218 -0
- package/convex/asset-replace-delete-race.test.ts +127 -0
- package/convex/assets.ts +27 -2
- package/convex/auth.ts +5 -2
- package/convex/authority.ts +35 -11
- package/convex/contract-parity.test.ts +7 -0
- package/convex/delivery-redirects.test.ts +167 -0
- package/convex/delivery.ts +23 -10
- package/convex/guard.ts +9 -7
- package/convex/http-routes.test.ts +18 -8
- package/convex/http.ts +23 -17
- package/convex/ingest.test.ts +326 -0
- package/convex/ingest.ts +281 -0
- package/convex/invites.ts +13 -5
- package/convex/members.ts +10 -4
- package/convex/rail-write-parity.test.ts +6 -6
- package/convex/record-functions.ts +325 -0
- package/convex/records.test.ts +156 -0
- package/convex/redirect-migration.test.ts +205 -0
- package/convex/redirect-revalidate.test.ts +5 -1
- package/convex/redirects.ts +120 -79
- package/convex/role-policy.test.ts +315 -0
- package/convex/roles.ts +83 -0
- package/convex/schema.ts +36 -0
- package/convex/triggers.ts +21 -1
- package/dist/addon.d.ts +71 -0
- package/dist/addon.js +67 -0
- package/dist/addon.js.map +1 -0
- package/dist/components/account/account-settings.js +2 -2
- package/dist/components/account/account-settings.js.map +1 -1
- package/dist/components/account/credential-panel.js +2 -2
- package/dist/components/account/credential-panel.js.map +1 -1
- package/dist/components/account/self-account-schema.d.ts +6 -15
- package/dist/components/account/self-account-schema.js +52 -34
- package/dist/components/account/self-account-schema.js.map +1 -1
- package/dist/components/account/self-account-view-source.d.ts +1 -1
- package/dist/components/account/self-account-view-source.js +6 -4
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
- package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
- package/dist/components/ai/ai-chat-panel.js +6 -6
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/ai-cms-panel.js +2 -2
- package/dist/components/ai/ai-cms-panel.js.map +1 -1
- package/dist/components/ai/ai-panel-toggle.js +4 -4
- package/dist/components/ai/ai-panel-toggle.js.map +1 -1
- package/dist/components/ai/chat-ui.js +2 -2
- package/dist/components/ai/chat-ui.js.map +1 -1
- package/dist/components/ai/proposal-card.d.ts +2 -2
- package/dist/components/ai/proposal-card.js +2 -2
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/proposal-issues.js +2 -2
- package/dist/components/ai/proposal-issues.js.map +1 -1
- package/dist/components/ai/translate-document-action.js +2 -2
- package/dist/components/ai/translate-document-action.js.map +1 -1
- package/dist/components/ai/translate-document-dialog.js +6 -6
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/ai/translate-skipped.d.ts +1 -1
- package/dist/components/ai/translate-skipped.js +2 -2
- package/dist/components/ai/translate-skipped.js.map +1 -1
- package/dist/components/app-views/default-modules.d.ts +50 -2
- package/dist/components/app-views/default-modules.js +65 -20
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +53 -3
- package/dist/components/app-views/module.js +14 -7
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.js +1 -1
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +11 -2
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-detail.js +28 -13
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/asset-grid.js +2 -2
- package/dist/components/assets/asset-grid.js.map +1 -1
- package/dist/components/assets/asset-locale.d.ts +9 -0
- package/dist/components/assets/asset-locale.js +16 -0
- package/dist/components/assets/asset-locale.js.map +1 -0
- package/dist/components/assets/asset-meta-label.d.ts +12 -0
- package/dist/components/assets/asset-meta-label.js +15 -0
- package/dist/components/assets/asset-meta-label.js.map +1 -0
- package/dist/components/assets/asset-metadata.d.ts +69 -15
- package/dist/components/assets/asset-metadata.js +99 -11
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-search.js +8 -2
- package/dist/components/assets/asset-search.js.map +1 -1
- package/dist/components/assets/asset-thumb.js +9 -5
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +25 -11
- package/dist/components/assets/assets-collection-view-source.js +78 -26
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-module.js +1 -1
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +2 -2
- package/dist/components/assets/assets-store.js +22 -9
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.js +1 -1
- package/dist/components/assets/assets-view-preference.js.map +1 -1
- package/dist/components/assets/replace-file.js +1 -1
- package/dist/components/assets/replace-file.js.map +1 -1
- package/dist/components/auth-context.d.ts +1 -1
- package/dist/components/auth-context.js +6 -6
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +1 -1
- package/dist/components/builder/blocks-model.js +3 -3
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +1 -1
- package/dist/components/builder/blocks-panel.js +4 -4
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +21 -0
- package/dist/components/builder/builder-store.js +24 -2
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-breakpoints.js +1 -1
- package/dist/components/builder/canvas-breakpoints.js.map +1 -1
- package/dist/components/builder/canvas-commit-boundary.d.ts +22 -0
- package/dist/components/builder/canvas-commit-boundary.js +27 -0
- package/dist/components/builder/canvas-commit-boundary.js.map +1 -0
- package/dist/components/builder/canvas-dnd.d.ts +2 -2
- package/dist/components/builder/canvas-dnd.js +1 -1
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
- package/dist/components/builder/canvas-inline-editing.js.map +1 -1
- package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
- package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
- package/dist/components/builder/canvas-rebase.d.ts +77 -0
- package/dist/components/builder/canvas-rebase.js +169 -0
- package/dist/components/builder/canvas-rebase.js.map +1 -0
- package/dist/components/builder/canvas-render-context.d.ts +7 -4
- package/dist/components/builder/canvas-render-context.js +21 -13
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas-theme-notice.js +2 -2
- package/dist/components/builder/canvas-theme-notice.js.map +1 -1
- package/dist/components/builder/canvas.js +3 -3
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +2 -2
- package/dist/components/builder/clipboard-actions.js +4 -4
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +21 -3
- package/dist/components/builder/composition-field-editor.js +48 -30
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/drag-session.d.ts +1 -1
- package/dist/components/builder/drag-session.js.map +1 -1
- package/dist/components/builder/fixture-thumbnail.js +1 -1
- package/dist/components/builder/fixture-thumbnail.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +1 -1
- package/dist/components/builder/insert-context.js +3 -3
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/layout-inspector.js +1 -1
- package/dist/components/builder/layout-inspector.js.map +1 -1
- package/dist/components/builder/node-factory.js +15 -3
- package/dist/components/builder/node-factory.js.map +1 -1
- package/dist/components/builder/node-title.js +1 -1
- package/dist/components/builder/node-title.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +1 -1
- package/dist/components/builder/page-tree.js +5 -5
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/placement-refusal.js +3 -3
- package/dist/components/builder/placement-refusal.js.map +1 -1
- package/dist/components/builder/property-inspector.js +7 -5
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +4 -4
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.js +1 -1
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/builder/rich-text-toolbar.js +1 -1
- package/dist/components/builder/rich-text-toolbar.js.map +1 -1
- package/dist/components/builder/run-inspector.js +11 -7
- package/dist/components/builder/run-inspector.js.map +1 -1
- package/dist/components/builder/selection-overlay.js +3 -3
- package/dist/components/builder/selection-overlay.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +2 -2
- package/dist/components/builder/spacing-handles.js +3 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +5 -2
- package/dist/components/cms/asset-field.js +36 -19
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker.js +5 -3
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +1 -1
- package/dist/components/cms/bulk-actions.js +2 -2
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/bulk-bar.d.ts +2 -2
- package/dist/components/cms/bulk-bar.js +2 -2
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/canvas-commit.d.ts +56 -0
- package/dist/components/cms/canvas-commit.js +35 -0
- package/dist/components/cms/canvas-commit.js.map +1 -0
- package/dist/components/cms/cell-edit.d.ts +1 -1
- package/dist/components/cms/cell-edit.js +1 -1
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cell.d.ts +1 -1
- package/dist/components/cms/cell.js +3 -3
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-index.js +2 -2
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-labels.js +2 -2
- package/dist/components/cms/cms-labels.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +2 -2
- package/dist/components/cms/cms-store.js +10 -10
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +30 -14
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/commit-policy.d.ts +6 -2
- package/dist/components/cms/commit-policy.js +1 -1
- package/dist/components/cms/commit-policy.js.map +1 -1
- package/dist/components/cms/detail-editor.d.ts +5 -2
- package/dist/components/cms/detail-editor.js +31 -15
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/details-pane.js +1 -1
- package/dist/components/cms/details-pane.js.map +1 -1
- package/dist/components/cms/doc-state.js +1 -1
- package/dist/components/cms/doc-state.js.map +1 -1
- package/dist/components/cms/document-diff-view.js +5 -5
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/cms/document-editor-context.js +1 -1
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/document-staging.js +1 -1
- package/dist/components/cms/document-staging.js.map +1 -1
- package/dist/components/cms/document-url-path.js +2 -2
- package/dist/components/cms/document-url-path.js.map +1 -1
- package/dist/components/cms/duplicate-document.d.ts +1 -1
- package/dist/components/cms/duplicate-document.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -3
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-collaboration.d.ts +1 -1
- package/dist/components/cms/field-collaboration.js +1 -1
- package/dist/components/cms/field-collaboration.js.map +1 -1
- package/dist/components/cms/field-connection.d.ts +1 -1
- package/dist/components/cms/field-connection.js +2 -2
- package/dist/components/cms/field-connection.js.map +1 -1
- package/dist/components/cms/field-control-lib.js +1 -1
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +4 -4
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.d.ts +1 -1
- package/dist/components/cms/field-renderer.js +2 -2
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/filter-builder.d.ts +1 -1
- package/dist/components/cms/filter-builder.js +3 -3
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/gate-validation.js +1 -1
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/import-export.js +2 -2
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/issue-copy.js +1 -1
- package/dist/components/cms/issue-copy.js.map +1 -1
- package/dist/components/cms/issues-pane.js +3 -3
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/object-array-field.js +2 -2
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.js +2 -2
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/reference-confirm.d.ts +1 -1
- package/dist/components/cms/reference-confirm.js +2 -2
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/relation-picker.js +2 -2
- package/dist/components/cms/relation-picker.js.map +1 -1
- package/dist/components/cms/rich-text/block-style.js +1 -1
- package/dist/components/cms/rich-text/block-style.js.map +1 -1
- package/dist/components/cms/rich-text/component-embed.js +4 -4
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.js +3 -3
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/embed-picker.js +3 -3
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.js +3 -3
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/cms/rich-text/extensions.js +1 -1
- package/dist/components/cms/rich-text/extensions.js.map +1 -1
- package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
- package/dist/components/cms/rich-text/font-size-control.js +2 -2
- package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
- package/dist/components/cms/rich-text/lazy.js +1 -1
- package/dist/components/cms/rich-text/lazy.js.map +1 -1
- package/dist/components/cms/rich-text/link-control.js +2 -2
- package/dist/components/cms/rich-text/link-control.js.map +1 -1
- package/dist/components/cms/rich-text/slash-command.js +3 -3
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.js +1 -1
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/rich-text/unknown-node.js +2 -2
- package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
- package/dist/components/cms/rich-text-preview.js +2 -2
- package/dist/components/cms/rich-text-preview.js.map +1 -1
- package/dist/components/cms/row-actions.d.ts +1 -1
- package/dist/components/cms/row-actions.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.d.ts +1 -1
- package/dist/components/cms/scalar-value-editor.js +2 -2
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/cms/use-discard-controller.d.ts +1 -1
- package/dist/components/cms/use-discard-controller.js +1 -1
- package/dist/components/cms/use-discard-controller.js.map +1 -1
- package/dist/components/cms/use-document-draft.d.ts +69 -5
- package/dist/components/cms/use-document-draft.js +118 -34
- package/dist/components/cms/use-document-draft.js.map +1 -1
- package/dist/components/cms/use-document-versions.js +1 -1
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/use-field-focus.d.ts +1 -1
- package/dist/components/cms/use-field-focus.js +2 -2
- package/dist/components/cms/use-field-focus.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +7 -2
- package/dist/components/cms/use-live-url.js +15 -12
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.d.ts +1 -1
- package/dist/components/cms/use-publish-controller.js +1 -1
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/cms/use-published-perspective.d.ts +1 -1
- package/dist/components/cms/use-published-perspective.js.map +1 -1
- package/dist/components/cms/use-restore-version.d.ts +1 -1
- package/dist/components/cms/use-restore-version.js +1 -1
- package/dist/components/cms/use-restore-version.js.map +1 -1
- package/dist/components/cms/version-compare-lazy.js +1 -1
- package/dist/components/cms/version-compare-lazy.js.map +1 -1
- package/dist/components/cms/version-compare.js +1 -1
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-pane.js +3 -3
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.js +3 -3
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +11 -7
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/collection-view/collection-view-context.d.ts +1 -1
- package/dist/components/collection-view/collection-view-context.js +1 -1
- package/dist/components/collection-view/collection-view-context.js.map +1 -1
- package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
- package/dist/components/collection-view/collection-view-surface.js +18 -0
- package/dist/components/collection-view/collection-view-surface.js.map +1 -0
- package/dist/components/components/component-detail.js +10 -10
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.d.ts +2 -2
- package/dist/components/components/component-preview.js +5 -5
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/component-status.d.ts +1 -1
- package/dist/components/components/component-status.js +1 -1
- package/dist/components/components/component-status.js.map +1 -1
- package/dist/components/components/components-index.js +2 -2
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/field-inspector.js +1 -1
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/components/registry-browser.js +3 -3
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.d.ts +1 -1
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +3 -3
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/current-user-menu.js +1 -1
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/delivery-health-banner.js +2 -2
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/deployment-setup-gate.js +13 -4
- package/dist/components/deployment-setup-gate.js.map +1 -1
- package/dist/components/dev-login-badge.js +2 -2
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/guards.js +1 -1
- package/dist/components/guards.js.map +1 -1
- package/dist/components/inspector/document-panes.d.ts +54 -0
- package/dist/components/inspector/document-panes.js +41 -0
- package/dist/components/inspector/document-panes.js.map +1 -0
- package/dist/components/inspector/inspector-sidebar.js +1 -1
- package/dist/components/inspector/inspector-sidebar.js.map +1 -1
- package/dist/components/nav-issues-registry.d.ts +53 -0
- package/dist/components/nav-issues-registry.js +69 -0
- package/dist/components/nav-issues-registry.js.map +1 -0
- package/dist/components/nav-issues.js +4 -4
- package/dist/components/nav-issues.js.map +1 -1
- package/dist/components/operation-error.d.ts +1 -1
- package/dist/components/operation-error.js +4 -4
- package/dist/components/operation-error.js.map +1 -1
- package/dist/components/playground-badge.js +2 -2
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +3 -3
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +2 -2
- package/dist/components/project-session.js +5 -5
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +2 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-store-status.js +1 -1
- package/dist/components/rail-store-status.js.map +1 -1
- package/dist/components/rail-view-source.d.ts +6 -5
- package/dist/components/rail-view-source.js +4 -3
- package/dist/components/rail-view-source.js.map +1 -1
- package/dist/components/records/record-lens.d.ts +51 -0
- package/dist/components/records/record-lens.js +151 -0
- package/dist/components/records/record-lens.js.map +1 -0
- package/dist/components/records/record-rail.d.ts +86 -0
- package/dist/components/records/record-rail.js +241 -0
- package/dist/components/records/record-rail.js.map +1 -0
- package/dist/components/redirects/redirects-schema.js +1 -1
- package/dist/components/redirects/redirects-schema.js.map +1 -1
- package/dist/components/references/references-panel.js +2 -2
- package/dist/components/references/references-panel.js.map +1 -1
- package/dist/components/section-nav.js +27 -11
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-state.js +2 -2
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/section-surface.js +2 -2
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/session-expired-overlay.js +3 -3
- package/dist/components/session-expired-overlay.js.map +1 -1
- package/dist/components/settings-form.js +1 -1
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/sign-in-needed-badge.js +1 -1
- package/dist/components/sign-in-needed-badge.js.map +1 -1
- package/dist/components/sign-in-view.d.ts +2 -2
- package/dist/components/sign-in-view.js +5 -5
- package/dist/components/sign-in-view.js.map +1 -1
- package/dist/components/studio-context.js +3 -3
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-project-screen.js +30 -3
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.js +6 -5
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/theme-context.d.ts +1 -1
- package/dist/components/theme-context.js +2 -2
- package/dist/components/theme-context.js.map +1 -1
- package/dist/components/use-hydration-phase.d.ts +1 -1
- package/dist/components/use-hydration-phase.js +1 -1
- package/dist/components/use-hydration-phase.js.map +1 -1
- package/dist/components/users/invite-user-bar.d.ts +27 -0
- package/dist/components/users/invite-user-bar.js +25 -9
- package/dist/components/users/invite-user-bar.js.map +1 -1
- package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
- package/dist/components/users/role-policy-drift-issues.js +71 -0
- package/dist/components/users/role-policy-drift-issues.js.map +1 -0
- package/dist/components/users/users-collection-view-source.d.ts +1 -1
- package/dist/components/users/users-collection-view-source.js +4 -2
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/components/users/users-module.js +1 -1
- package/dist/components/users/users-module.js.map +1 -1
- package/dist/components/users/users-schema.d.ts +10 -1
- package/dist/components/users/users-schema.js +45 -25
- package/dist/components/users/users-schema.js.map +1 -1
- package/dist/components/users/users-store.js +3 -3
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/components/workspace-projects.d.ts +1 -1
- package/dist/components/workspace-projects.js +1 -1
- package/dist/components/workspace-projects.js.map +1 -1
- package/dist/lib/ai/index.d.ts +1 -1
- package/dist/lib/ai/index.js +1 -1
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/node-handler.d.ts +1 -1
- package/dist/lib/ai/node-handler.js.map +1 -1
- package/dist/lib/ai/translate-document.js +1 -1
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/assets/upload.d.ts +25 -7
- package/dist/lib/assets/upload.js +30 -21
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.js +1 -1
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +2 -2
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/published-documents.d.ts +24 -6
- package/dist/lib/cms/published-documents.js +39 -6
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +12 -99
- package/dist/lib/cms-views/view-model.js +22 -103
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +13 -3
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/content-session-refresh.d.ts +1 -1
- package/dist/lib/data/content-session-refresh.js.map +1 -1
- package/dist/lib/data/playground-unload-guard.d.ts +1 -1
- package/dist/lib/data/playground-unload-guard.js +1 -1
- package/dist/lib/data/playground-unload-guard.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/i18n.js +1 -1
- package/dist/lib/i18n/i18n.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +11 -3
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +9 -1
- package/dist/lib/i18n/messages.en.js +16 -4
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/config-manifest-lookup.d.ts +1 -1
- package/dist/lib/operations/config-manifest-lookup.js +3 -3
- package/dist/lib/operations/config-manifest-lookup.js.map +1 -1
- package/dist/lib/operations/project-runtime-config.d.ts +9 -0
- package/dist/lib/operations/project-runtime-config.js +13 -0
- package/dist/lib/operations/project-runtime-config.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +9 -1
- package/dist/lib/operations/studio-operations.js +6 -1
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +16 -3
- package/dist/lib/persistence/convex-adapter.js +78 -47
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/deployment-readiness.js +14 -11
- package/dist/lib/persistence/deployment-readiness.js.map +1 -1
- package/dist/lib/persistence/index.js +14 -1
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.js +2 -1
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/query/index.d.ts +2 -2
- package/dist/lib/query/index.js +1 -1
- package/dist/lib/query/index.js.map +1 -1
- package/dist/lib/query/operations-query.d.ts +27 -1
- package/dist/lib/query/operations-query.js +13 -1
- package/dist/lib/query/operations-query.js.map +1 -1
- package/dist/lib/references/reference-model.js +1 -1
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +1 -1
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/registry/index.js +2 -2
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/preview.d.ts +10 -0
- package/dist/lib/registry/preview.js +13 -4
- package/dist/lib/registry/preview.js.map +1 -1
- package/dist/lib/routes/route-issues.js +6 -3
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +6 -0
- package/dist/lib/workspace/index.js +8 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/workspace-source.js +6 -7
- package/dist/lib/workspace/workspace-source.js.map +1 -1
- package/dist/mount.d.ts +28 -0
- package/dist/mount.js +27 -0
- package/dist/mount.js.map +1 -0
- package/dist/previews/entry-node.d.ts +14 -0
- package/dist/previews/entry-node.js +22 -0
- package/dist/previews/entry-node.js.map +1 -0
- package/dist/previews/harness.js +2 -16
- package/dist/previews/harness.js.map +1 -1
- package/dist/router/root.js +10 -10
- package/dist/router/root.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/button.d.ts +2 -2
- package/package.json +15 -11
- package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
- package/dist/components/redirects/redirects-collection-view-source.js +0 -146
- package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
- package/dist/components/redirects/redirects-module.d.ts +0 -21
- package/dist/components/redirects/redirects-module.js +0 -40
- package/dist/components/redirects/redirects-module.js.map +0 -1
- package/dist/components/redirects/redirects-store.d.ts +0 -68
- package/dist/components/redirects/redirects-store.js +0 -132
- package/dist/components/redirects/redirects-store.js.map +0 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { convexTest } from 'convex-test'
|
|
3
|
+
import { makeFunctionReference } from 'convex/server'
|
|
4
|
+
import type { FunctionReference } from 'convex/server'
|
|
5
|
+
import type { ConvexHttpClient } from 'convex/browser'
|
|
6
|
+
// The host's two readers, from their own modules — the package index also loads the React
|
|
7
|
+
// delivery kit, which the edge-runtime environment cannot evaluate.
|
|
8
|
+
import { assembleLiveSource } from '../../next/src/host-content'
|
|
9
|
+
import { createContentSnapshot } from '../../next/src/snapshot'
|
|
10
|
+
import { ConvexPersistenceAdapter } from '../src/lib/persistence/convex-adapter'
|
|
11
|
+
import schema from './schema'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The delivery snapshot's redirect strand, end to end: rows stored in the deployment's
|
|
15
|
+
* `records` table (kind `redirect`) → the REAL `delivery:snapshot` query (convex-test) →
|
|
16
|
+
* the REAL host loader (`assembleLiveSource`, `@elytracms/next`).
|
|
17
|
+
*
|
|
18
|
+
* EC-628 (Codex F04, `tasks/codex-review-2026-10-27/backend.convex.test.ts` "F04 upstream"):
|
|
19
|
+
* the record store's open data bag accepts drift the normal CRUD refuses (a dashboard edit,
|
|
20
|
+
* an older writer). Before, an enabled row with `to: 42` was SERVED by the snapshot and
|
|
21
|
+
* vanished in the host with zero issues, and a switch that is not a boolean was decided by
|
|
22
|
+
* truthiness (`'false'` served, a missing switch silently paused). Now the snapshot reads
|
|
23
|
+
* every stored redirect through the one rule (`readLiveRedirects`): what it cannot read is
|
|
24
|
+
* withheld and reported on the snapshot's `redirectIssues` strand, which the host carries
|
|
25
|
+
* into its `sourceIssues` and onto the 404 at the redirect's `from`.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const modules = {
|
|
29
|
+
...import.meta.glob(['./**/*.ts', '!./**/*.test.ts', '!./_generated/**']),
|
|
30
|
+
'./_generated/api.ts': async () => ({}),
|
|
31
|
+
}
|
|
32
|
+
const snapshot = makeFunctionReference<'query'>('delivery:snapshot')
|
|
33
|
+
|
|
34
|
+
const AT = '2026-10-27T12:00:00.000Z'
|
|
35
|
+
const LOCALES = { default: 'en', locales: ['en'] }
|
|
36
|
+
|
|
37
|
+
/** Insert redirect rows as the record store holds them — raw, as drift reaches it. */
|
|
38
|
+
async function deploymentWith(rows: ReadonlyArray<{ id: string; data: Record<string, unknown> }>) {
|
|
39
|
+
const t = convexTest(schema, modules)
|
|
40
|
+
await t.run(async (ctx) => {
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
await ctx.db.insert('records', { kind: 'redirect', recordId: row.id, data: row.data, at: AT, createdAt: AT, updatedAt: AT })
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
return t
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const redirect = (id: string, data: Record<string, unknown> = {}) => ({
|
|
49
|
+
id,
|
|
50
|
+
data: { from: `/${id}`, to: '/target', permanent: true, enabled: true, ...data },
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('EC-628 — the snapshot names every stored redirect it cannot serve', () => {
|
|
54
|
+
it('drift is withheld and reported upstream, then located by the host (F04 + disabled-state drift)', async () => {
|
|
55
|
+
const t = await deploymentWith([
|
|
56
|
+
redirect('good'),
|
|
57
|
+
redirect('bad', { to: 42 }), // F04's row
|
|
58
|
+
redirect('paused', { enabled: false }), // a paused redirect: not live, not a finding
|
|
59
|
+
redirect('string-off', { enabled: 'false' }), // truthy: was SERVED before
|
|
60
|
+
{ id: 'no-switch', data: { from: '/no-switch', to: '/target', permanent: true } }, // falsy: was silently paused
|
|
61
|
+
])
|
|
62
|
+
const payload = (await t.query(snapshot, { projectId: 'site' })) as {
|
|
63
|
+
redirects: unknown[]
|
|
64
|
+
redirectIssues: Array<{ code: string; path: unknown[] }>
|
|
65
|
+
}
|
|
66
|
+
expect(payload.redirects).toEqual([{ id: 'good', from: '/good', to: '/target', permanent: true }])
|
|
67
|
+
expect(payload.redirectIssues.map((issue) => [issue.code, issue.path])).toEqual([
|
|
68
|
+
['invalid-redirect', ['redirects', 'bad']],
|
|
69
|
+
['invalid-redirect', ['redirects', 'no-switch']],
|
|
70
|
+
['invalid-redirect', ['redirects', 'string-off']],
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
const client = assembleLiveSource(payload as never, { locales: LOCALES, collections: [], routes: [] }, 'published').client({
|
|
74
|
+
perspective: 'published',
|
|
75
|
+
})
|
|
76
|
+
// Control: the readable redirect traverses the whole pipeline, with no issue.
|
|
77
|
+
expect(client.resolvePage('/good').status).toBe('redirect')
|
|
78
|
+
expect(client.resolvePage('/good').issues).toEqual([])
|
|
79
|
+
expect(client.sourceIssues.map((issue) => issue.path[1])).toEqual(['bad', 'no-switch', 'string-off'])
|
|
80
|
+
for (const path of ['/bad', '/string-off', '/no-switch']) {
|
|
81
|
+
const result = client.resolvePage(path)
|
|
82
|
+
expect(result.status).toBe('notFound')
|
|
83
|
+
expect(result.issues.map((issue) => issue.code)).toEqual(['invalid-redirect'])
|
|
84
|
+
}
|
|
85
|
+
// The paused redirect is a 404 with nothing to say — pausing is not drift.
|
|
86
|
+
expect(client.resolvePage('/paused').status).toBe('notFound')
|
|
87
|
+
expect(client.resolvePage('/paused').issues).toEqual([])
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* EC-627 — "which redirects are live" is decided by ONE reader on every side. The same
|
|
93
|
+
* stored set is read (1) by the Convex delivery snapshot (`delivery:snapshot`, through
|
|
94
|
+
* `readLiveRedirects`) and assembled by the host's live loader, and (2) by the host's OTHER
|
|
95
|
+
* reader, `createContentSnapshot` (`next/src/snapshot.ts`, `readLiveRedirects` over
|
|
96
|
+
* `adapter.records(redirectRecordKind).list`) — through the REAL `ConvexPersistenceAdapter`
|
|
97
|
+
* over the same convex-test deployment, so both sides read the very same stored rows.
|
|
98
|
+
*
|
|
99
|
+
* The set holds what a hand-built copy of the rule gets wrong: a paused redirect (a copy that
|
|
100
|
+
* forgets the switch serves it), a switch that is not a boolean (a truthy copy serves
|
|
101
|
+
* `'false'`), a duplicate `from` (`/old` and `/old/` — the lower id applies on both sides)
|
|
102
|
+
* and an editorial `meta` bag (never on the routing record). Measured: replacing either
|
|
103
|
+
* side's call with a hand-built `.filter((r) => r.enabled)` projection turns this red.
|
|
104
|
+
*/
|
|
105
|
+
describe('EC-627 — the Convex snapshot and the host adapter snapshot serve one live set', () => {
|
|
106
|
+
const STORED = [
|
|
107
|
+
redirect('a', { meta: { note: 'editorial only' } }),
|
|
108
|
+
redirect('paused', { enabled: false }),
|
|
109
|
+
redirect('string-off', { enabled: 'false' }),
|
|
110
|
+
{ id: 'dup-1', data: { from: '/old', to: '/one', permanent: true, enabled: true } },
|
|
111
|
+
{ id: 'dup-2', data: { from: '/old/', to: '/two', permanent: false, enabled: true } },
|
|
112
|
+
]
|
|
113
|
+
const PATHS = ['/a', '/paused', '/string-off', '/old', '/old/']
|
|
114
|
+
|
|
115
|
+
/** The decision a reader makes per URL: what a visitor gets. */
|
|
116
|
+
const outcomes = (client: { resolvePage(url: string): { status: string; redirect: unknown } }) =>
|
|
117
|
+
PATHS.map((path) => {
|
|
118
|
+
const result = client.resolvePage(path)
|
|
119
|
+
return { path, status: result.status, redirect: result.redirect }
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('the same stored set yields the same live set through both readers', async () => {
|
|
123
|
+
const t = await deploymentWith(STORED)
|
|
124
|
+
await t.run(async (ctx) => {
|
|
125
|
+
await ctx.db.insert('members', { userId: 'admin', role: 'admin', createdAt: AT, updatedAt: AT })
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// (1) the deployment's public snapshot → the host's live loader
|
|
129
|
+
const payload = await t.query(snapshot, { projectId: 'site' })
|
|
130
|
+
const viaSnapshot = assembleLiveSource(payload as never, { locales: LOCALES, collections: [], routes: [] }, 'published')
|
|
131
|
+
.client({ perspective: 'published' })
|
|
132
|
+
|
|
133
|
+
// (2) the host's adapter reader over the same deployment
|
|
134
|
+
const asAdmin = t.withIdentity({ subject: 'admin', issuer: 'https://convex.test' })
|
|
135
|
+
const adapter = new ConvexPersistenceAdapter(
|
|
136
|
+
{
|
|
137
|
+
query: (ref: FunctionReference<'query'>, args?: Record<string, unknown>) => asAdmin.query(ref, args ?? {}),
|
|
138
|
+
mutation: (ref: FunctionReference<'mutation'>, args?: Record<string, unknown>) => asAdmin.mutation(ref, args ?? {}),
|
|
139
|
+
} as unknown as ConvexHttpClient,
|
|
140
|
+
'site',
|
|
141
|
+
)
|
|
142
|
+
const viaAdapter = (await createContentSnapshot(adapter, 'site', { locales: LOCALES, collections: [], routes: [] }))
|
|
143
|
+
.client({ perspective: 'published' })
|
|
144
|
+
|
|
145
|
+
// The instrument can tell outcomes apart — pinned explicitly, not only compared.
|
|
146
|
+
expect(outcomes(viaSnapshot)).toEqual([
|
|
147
|
+
{ path: '/a', status: 'redirect', redirect: { target: '/target', permanent: true } },
|
|
148
|
+
{ path: '/paused', status: 'notFound', redirect: null },
|
|
149
|
+
{ path: '/string-off', status: 'notFound', redirect: null },
|
|
150
|
+
{ path: '/old', status: 'redirect', redirect: { target: '/one', permanent: true } },
|
|
151
|
+
{ path: '/old/', status: 'redirect', redirect: { target: '/one', permanent: true } },
|
|
152
|
+
])
|
|
153
|
+
expect(outcomes(viaAdapter)).toEqual(outcomes(viaSnapshot))
|
|
154
|
+
// The duplicate is one issue, the same on both sides (the router's one redirect set).
|
|
155
|
+
const duplicates = (issues: readonly { code: string; path: readonly unknown[] }[]) =>
|
|
156
|
+
issues.filter((issue) => issue.code === 'invalid-redirect').map((issue) => issue.path)
|
|
157
|
+
expect(duplicates(viaSnapshot.routerIssues)).toEqual([['redirects', 'dup-2']])
|
|
158
|
+
expect(duplicates(viaAdapter.routerIssues)).toEqual(duplicates(viaSnapshot.routerIssues))
|
|
159
|
+
|
|
160
|
+
// EC-628 (the adapter snapshot): the drifted row is NAMED on both sides, not only withheld —
|
|
161
|
+
// on the source and on the 404 at its `from`. (Before, the adapter path withheld it silently.)
|
|
162
|
+
const sourcePaths = (client: typeof viaSnapshot) => client.sourceIssues.map((issue) => [issue.code, issue.path])
|
|
163
|
+
expect(sourcePaths(viaSnapshot)).toEqual([['invalid-redirect', ['redirects', 'string-off']]])
|
|
164
|
+
expect(sourcePaths(viaAdapter)).toEqual(sourcePaths(viaSnapshot))
|
|
165
|
+
expect(viaAdapter.resolvePage('/string-off').issues.map((issue) => issue.code)).toEqual(['invalid-redirect'])
|
|
166
|
+
})
|
|
167
|
+
})
|
package/convex/delivery.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { v } from 'convex/values'
|
|
2
|
-
import { DRAFT_PERSPECTIVE, DRAFT_VARIANT, unfoldValue, variantOf } from '@elytracms/core/cms-core'
|
|
2
|
+
import { DRAFT_PERSPECTIVE, DRAFT_VARIANT, readLiveRedirects, unfoldValue, variantOf } from '@elytracms/core/cms-core'
|
|
3
3
|
import type { Perspective } from '@elytracms/core/cms-core'
|
|
4
4
|
import { assetRecordFromRow, deliveryAssetRecord } from '@elytracms/core/persistence'
|
|
5
|
+
import { constantTimeEquals } from '@elytracms/core/revalidation'
|
|
5
6
|
import type { AssetRecord } from '@elytracms/core/persistence'
|
|
6
7
|
import studioPackage from '../package.json'
|
|
7
8
|
import { query } from './functions'
|
|
8
9
|
import type { QueryCtx } from './functions'
|
|
9
10
|
import { requireCapability } from './guard'
|
|
11
|
+
import { loadRedirectEntries } from './redirects'
|
|
10
12
|
|
|
11
13
|
// EC-207: routes/redirects are config-sourced (authored in elytra.config.ts and
|
|
12
14
|
// resolved by studio-core), NOT backend data. The delivery snapshot no longer
|
|
@@ -75,6 +77,12 @@ interface DeliverySnapshot {
|
|
|
75
77
|
* infra redirects stay native (`next.config`/`vercel`), evaluated before these.
|
|
76
78
|
*/
|
|
77
79
|
redirects: unknown[]
|
|
80
|
+
/**
|
|
81
|
+
* EC-628 — one `invalid-redirect` issue per stored redirect the live rule withheld because
|
|
82
|
+
* it could not read it (id, `from` and the kind of fault — never the target or a value).
|
|
83
|
+
* Paused redirects are not here: pausing is not drift.
|
|
84
|
+
*/
|
|
85
|
+
redirectIssues: unknown[]
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/**
|
|
@@ -156,20 +164,23 @@ export async function loadSnapshot(
|
|
|
156
164
|
|
|
157
165
|
const assets = await loadDeliveryAssets(ctx, projectId)
|
|
158
166
|
|
|
159
|
-
// Editorial redirects (EC-303): only ENABLED entries serve — a disabled redirect
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
// Editorial redirects (EC-303): only ENABLED entries serve — a disabled redirect is
|
|
168
|
+
// stored but never live, in either perspective. EC-616: read through the redirect
|
|
169
|
+
// kind's one server-side reader (records + the marked legacy tolerant read) and
|
|
170
|
+
// decided by the ONE live-redirect rule, `readLiveRedirects` (EC-590 / EC-627 — this
|
|
171
|
+
// was the last hand-built copy on Convex). EC-628: a stored redirect the rule cannot
|
|
172
|
+
// read (a non-boolean switch, a non-string `to`) is withheld AND reported — the host
|
|
173
|
+
// carries `redirectIssues` into its source issues — instead of being served or dropped
|
|
174
|
+
// by truthiness. The record kind's own read, not the project schema (EC-585 1e).
|
|
175
|
+
const live = readLiveRedirects(await loadRedirectEntries(ctx, projectId))
|
|
166
176
|
|
|
167
177
|
return {
|
|
168
178
|
projectId,
|
|
169
179
|
perspective,
|
|
170
180
|
documents,
|
|
171
181
|
assets,
|
|
172
|
-
redirects,
|
|
182
|
+
redirects: live.redirects,
|
|
183
|
+
redirectIssues: live.issues,
|
|
173
184
|
}
|
|
174
185
|
}
|
|
175
186
|
|
|
@@ -214,7 +225,9 @@ export const draftSnapshot = query({
|
|
|
214
225
|
if (expected === undefined || expected.length === 0) {
|
|
215
226
|
throw new Error('unauthorized: draft preview is not configured on this deployment')
|
|
216
227
|
}
|
|
217
|
-
|
|
228
|
+
// EC-617: the one timing-safe comparison (`constantTimeEquals`, the MAC-compare the
|
|
229
|
+
// revalidation and ingest verifiers rest on) — never a string `!==`.
|
|
230
|
+
if (!(await constantTimeEquals(args.token, expected))) {
|
|
218
231
|
throw new Error('unauthorized: invalid draft preview token')
|
|
219
232
|
}
|
|
220
233
|
return loadSnapshot(ctx, { projectId: args.projectId }, DRAFT_PERSPECTIVE)
|
package/convex/guard.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getAuthUserId } from '@convex-dev/auth/server'
|
|
2
|
-
import {
|
|
2
|
+
import { roleGrantsCapability } from '@elytracms/core/persistence'
|
|
3
3
|
import type { OperationCapabilityId, ProjectRole, RolePolicy } from '@elytracms/core/persistence'
|
|
4
4
|
import type { QueryCtx } from './functions'
|
|
5
|
+
import { deployedRolePolicy } from './roles'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Server-side authorization guards (EC-150). The operations chokepoint in the
|
|
@@ -18,9 +19,10 @@ import type { QueryCtx } from './functions'
|
|
|
18
19
|
* imported here rather than re-declared. Handlers gate on CAPABILITIES
|
|
19
20
|
* (`content.read` / `content.write` / `project.administer`), not literal roles
|
|
20
21
|
* (EC-240c), so a project's custom role set enforces server-side by the same
|
|
21
|
-
* policy the operations layer uses.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* policy the operations layer uses. EC-605: the policy is the project's `roles`
|
|
23
|
+
* declaration as DEPLOYED — the copy the `elytra` CLI provisions into
|
|
24
|
+
* `ELYTRA_ROLE_POLICY` (`./roles` `deployedRolePolicy`: the default when unset, a
|
|
25
|
+
* thrown, located error when malformed — never a silent default).
|
|
24
26
|
*
|
|
25
27
|
* Failures throw plain `Error('unauthorized: …')` — Convex transports the
|
|
26
28
|
* message; the app's adapters normalize thrown backend errors onto the
|
|
@@ -78,13 +80,13 @@ export async function memberRole(ctx: GuardCtx, userId: string): Promise<Project
|
|
|
78
80
|
* This is the ONE role-enforcement primitive at the trust boundary — every
|
|
79
81
|
* handler gates on a capability rather than a literal role, so a project's
|
|
80
82
|
* custom role set is honoured by the shared policy. `policy` defaults to the
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
+
* deployment's policy (EC-605, {@link deployedRolePolicy}); the parameter stays for a
|
|
84
|
+
* caller that must judge against another (tests).
|
|
83
85
|
*/
|
|
84
86
|
export async function requireCapability(
|
|
85
87
|
ctx: GuardCtx,
|
|
86
88
|
capability: OperationCapabilityId,
|
|
87
|
-
policy: RolePolicy =
|
|
89
|
+
policy: RolePolicy = deployedRolePolicy(),
|
|
88
90
|
): Promise<string> {
|
|
89
91
|
const userId = await requireUserId(ctx)
|
|
90
92
|
const actual = await memberRole(ctx, userId)
|
|
@@ -33,9 +33,20 @@ function routeTable(http: HttpRouter): string[] {
|
|
|
33
33
|
.sort()
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
/** EC-617: the engine's ONE route — the public-ingest receiver, an exact path (the EC-481 bar: a real client, a narrow prefix). */
|
|
37
|
+
const ENGINE_ROUTES = ['POST /elytra/ingest']
|
|
38
|
+
|
|
39
|
+
describe('defineStudioHttp — the engine registers exactly one HTTP route of its own (EC-481 → EC-617)', () => {
|
|
40
|
+
it('contributes ONLY the ingest receiver beyond what the injected auth registers', () => {
|
|
41
|
+
expect(routeTable(defineStudioHttp({ auth: silentAuth }))).toEqual(ENGINE_ROUTES)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('the receiver is an EXACT path, POST only — it claims no prefix', () => {
|
|
45
|
+
const http = defineStudioHttp({ auth: silentAuth })
|
|
46
|
+
expect(http.lookup('/elytra/ingest', 'POST')).not.toBeNull()
|
|
47
|
+
expect(http.lookup('/elytra/ingest/anything', 'POST')).toBeNull()
|
|
48
|
+
expect(http.lookup('/elytra/', 'POST')).toBeNull()
|
|
49
|
+
expect(http.lookup('/elytra/ingest', 'GET')).toBeNull()
|
|
39
50
|
})
|
|
40
51
|
|
|
41
52
|
it('control: the recorder DOES see routes, so the empty result above is real', () => {
|
|
@@ -45,10 +56,9 @@ describe('defineStudioHttp — the engine registers no HTTP routes of its own (E
|
|
|
45
56
|
http.route({ pathPrefix: '/api/auth/callback/', method: 'POST', handler })
|
|
46
57
|
},
|
|
47
58
|
}
|
|
48
|
-
expect(routeTable(defineStudioHttp({ auth }))).toEqual(
|
|
49
|
-
'GET /.well-known/jwks.json',
|
|
50
|
-
|
|
51
|
-
])
|
|
59
|
+
expect(routeTable(defineStudioHttp({ auth }))).toEqual(
|
|
60
|
+
['GET /.well-known/jwks.json', 'POST /api/auth/callback/*', ...ENGINE_ROUTES].sort(),
|
|
61
|
+
)
|
|
52
62
|
})
|
|
53
63
|
|
|
54
64
|
it('passes the auth routes through untouched — nothing is added, dropped, or reordered', () => {
|
|
@@ -61,7 +71,7 @@ describe('defineStudioHttp — the engine registers no HTTP routes of its own (E
|
|
|
61
71
|
}
|
|
62
72
|
},
|
|
63
73
|
}
|
|
64
|
-
expect(routeTable(defineStudioHttp({ auth }))).toEqual([...registered].sort())
|
|
74
|
+
expect(routeTable(defineStudioHttp({ auth }))).toEqual([...registered, ...ENGINE_ROUTES].sort())
|
|
65
75
|
})
|
|
66
76
|
})
|
|
67
77
|
|
package/convex/http.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { httpRouter } from 'convex/server'
|
|
2
2
|
import type { HttpRouter } from 'convex/server'
|
|
3
|
+
import { INGEST_RECEIVER_PATH } from '@elytracms/core/ingest'
|
|
4
|
+
import { ingestReceiver } from './ingest'
|
|
5
|
+
import type { IngestHandlers } from './ingest'
|
|
3
6
|
|
|
4
7
|
/** The slice of the Convex Auth object the studio's HTTP routes need. */
|
|
5
8
|
interface AuthHttpRoutes {
|
|
@@ -8,33 +11,36 @@ interface AuthHttpRoutes {
|
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* EC-252 — the studio's HTTP routes as a factory. Pure engine infra ships in the
|
|
11
|
-
* package; only the consumer's `auth` (provider choice)
|
|
12
|
-
* `convex/http.ts
|
|
14
|
+
* package; only the consumer's `auth` (provider choice) and — since EC-617 — its ingest
|
|
15
|
+
* handlers are injected. The consumer's `convex/http.ts`:
|
|
13
16
|
*
|
|
14
17
|
* ```ts
|
|
15
18
|
* import { auth } from './auth'
|
|
16
19
|
* export default defineStudioHttp({ auth })
|
|
20
|
+
* // with an addon that handles ingest payloads (one import, one entry):
|
|
21
|
+
* // import { formsIngest } from '@elytracms/forms/convex'
|
|
22
|
+
* // export default defineStudioHttp({ auth, ingest: { submission: formsIngest } })
|
|
17
23
|
* ```
|
|
18
24
|
*
|
|
19
|
-
* ## The engine registers
|
|
25
|
+
* ## The engine registers exactly ONE route of its own (EC-481 → EC-617)
|
|
20
26
|
*
|
|
21
|
-
* Everything on this router comes from Convex Auth's `addHttpRoutes` — the JWT
|
|
22
|
-
* discovery endpoints (`/.well-known/*`, EC-150) and, only when a consumer supplies
|
|
23
|
-
*
|
|
27
|
+
* Everything else on this router comes from Convex Auth's `addHttpRoutes` — the JWT
|
|
28
|
+
* discovery endpoints (`/.well-known/*`, EC-150) and, only when a consumer supplies an
|
|
29
|
+
* OAuth provider, `/api/auth/signin/*` + `/api/auth/callback/*`.
|
|
24
30
|
*
|
|
25
|
-
*
|
|
26
|
-
* registry-sync endpoint
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* narrower prefix than `/api/projects/`.
|
|
31
|
+
* EC-481 removed the engine's former `GET /api/projects/*` (a Bearer-authenticated
|
|
32
|
+
* registry-sync endpoint with no client left) and set the bar for any future one: "a
|
|
33
|
+
* real client and a narrower prefix". EC-617's public-ingest RECEIVER is that route:
|
|
34
|
+
* `POST /elytra/ingest` — an EXACT path, not a prefix; its client is the site's
|
|
35
|
+
* `createIngestRoute` (`@elytracms/next`), which signs every payload with the
|
|
36
|
+
* server-held secret, and `elytra doctor`'s probe. It is mounted on every deployment,
|
|
37
|
+
* with or without handlers: closed (401) until `ELYTRA_INGEST_SECRET` is set, and a
|
|
38
|
+
* payload kind with no registered handler is refused visibly (422), never dropped.
|
|
39
|
+
* `http-routes.test.ts` pins the table.
|
|
35
40
|
*/
|
|
36
|
-
export function defineStudioHttp({ auth }: { auth: AuthHttpRoutes }): HttpRouter {
|
|
41
|
+
export function defineStudioHttp({ auth, ingest }: { auth: AuthHttpRoutes; ingest?: IngestHandlers }): HttpRouter {
|
|
37
42
|
const http = httpRouter()
|
|
38
43
|
auth.addHttpRoutes(http)
|
|
44
|
+
http.route({ path: INGEST_RECEIVER_PATH, method: 'POST', handler: ingestReceiver(ingest) })
|
|
39
45
|
return http
|
|
40
46
|
}
|