@agent-native/core 0.92.6 → 0.92.8
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 +18 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/model-config.ts +30 -30
- package/corpus/core/src/agent/production-agent.ts +129 -7
- package/corpus/core/src/cli/design-connect.ts +438 -83
- package/corpus/core/src/cli/recap.ts +2 -2
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +8 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
- package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/org/TeamPage.tsx +41 -26
- package/corpus/core/src/client/use-action.ts +113 -1
- package/corpus/core/src/db/client.ts +12 -2
- package/corpus/core/src/db/create-get-db.ts +4 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/transcribe-voice.ts +1 -1
- package/corpus/core/src/usage/store.ts +13 -4
- package/corpus/core/src/vite/client.ts +38 -1
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
- package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
- package/corpus/templates/analytics/app/global.css +11 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
- package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
- package/corpus/templates/analytics/server/db/index.ts +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
- package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
- package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
- package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
- package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
- package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
- package/corpus/templates/design/actions/capture-design-state.ts +2 -2
- package/corpus/templates/design/actions/connect-localhost.ts +58 -12
- package/corpus/templates/design/actions/create-component.ts +2 -18
- package/corpus/templates/design/actions/create-design-branch.ts +38 -18
- package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
- package/corpus/templates/design/actions/delete-file.ts +19 -34
- package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
- package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
- package/corpus/templates/design/actions/export-pdf.ts +2 -1
- package/corpus/templates/design/actions/export-zip.ts +9 -3
- package/corpus/templates/design/actions/generate-design.ts +107 -70
- package/corpus/templates/design/actions/get-component-details.ts +2 -18
- package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
- package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
- package/corpus/templates/design/actions/get-design.ts +2 -1
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
- package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
- package/corpus/templates/design/actions/index-components.ts +2 -18
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
- package/corpus/templates/design/actions/list-local-files.ts +9 -2
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
- package/corpus/templates/design/actions/open-component-source.ts +2 -18
- package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
- package/corpus/templates/design/actions/present-design-variants.ts +70 -70
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
- package/corpus/templates/design/actions/read-local-file.ts +9 -2
- package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
- package/corpus/templates/design/actions/rename-screen.ts +425 -0
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
- package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
- package/corpus/templates/design/actions/update-design.ts +376 -61
- package/corpus/templates/design/actions/update-file.ts +455 -188
- package/corpus/templates/design/actions/write-local-file.ts +10 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
- package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
- package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
- package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
- package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
- package/corpus/templates/design/app/components/design/index.ts +2 -2
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
- package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
- package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
- package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
- package/corpus/templates/design/app/i18n-data.ts +223 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
- package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
- package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
- package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
- package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
- package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
- package/corpus/templates/design/server/db/index.ts +2 -52
- package/corpus/templates/design/server/db/schema.ts +16 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
- package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
- package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
- package/corpus/templates/design/server/plugins/db.ts +19 -0
- package/corpus/templates/design/server/source-workspace.ts +12 -13
- package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
- package/corpus/templates/design/shared/screen-rename.ts +42 -0
- package/corpus/templates/design/shared/source-mode.ts +84 -4
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/model-config.d.ts +9 -9
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +30 -30
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +60 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +106 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +30 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +359 -75
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/recap.js +2 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +1 -1
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +10 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +6 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/use-action.d.ts +32 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +68 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.js +1 -1
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +13 -4
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/docs/content/pr-visual-recap.mdx +3 -3
- package/docs/content/pure-agent-apps.mdx +22 -0
- package/package.json +2 -2
|
@@ -1,35 +1,285 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
|
-
import { eq } from "drizzle-orm";
|
|
3
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.optional()
|
|
20
|
-
.describe("Updated project type"),
|
|
21
|
-
designSystemId: z
|
|
8
|
+
const MAX_DATA_CAS_ATTEMPTS = 5;
|
|
9
|
+
const MAX_DATA_OPERATION_SOURCES = 128;
|
|
10
|
+
const FORBIDDEN_DATA_PATH_SEGMENTS = new Set([
|
|
11
|
+
"__proto__",
|
|
12
|
+
"constructor",
|
|
13
|
+
"prototype",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const dataPathSchema = z
|
|
17
|
+
.array(
|
|
18
|
+
z
|
|
22
19
|
.string()
|
|
23
20
|
.min(1)
|
|
24
|
-
.
|
|
25
|
-
.
|
|
26
|
-
|
|
21
|
+
.max(256)
|
|
22
|
+
.refine((segment) => !FORBIDDEN_DATA_PATH_SEGMENTS.has(segment), {
|
|
23
|
+
message: "Unsafe design data path segment",
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(8);
|
|
28
|
+
|
|
29
|
+
const dataOperationSchema = z.discriminatedUnion("op", [
|
|
30
|
+
z.object({
|
|
31
|
+
op: z.literal("set"),
|
|
32
|
+
path: dataPathSchema,
|
|
33
|
+
value: z.json(),
|
|
34
|
+
}),
|
|
35
|
+
z.object({
|
|
36
|
+
op: z.literal("delete"),
|
|
37
|
+
path: dataPathSchema,
|
|
27
38
|
}),
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
type DataOperation = z.infer<typeof dataOperationSchema>;
|
|
42
|
+
|
|
43
|
+
type DataOperationRevisions = Record<string, number>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Normalize affected-row metadata from every createGetDb backend: libSQL,
|
|
47
|
+
* PGlite, Neon, postgres.js, better-sqlite3, and D1.
|
|
48
|
+
*/
|
|
49
|
+
function affectedRowCount(result: unknown): number | undefined {
|
|
50
|
+
const candidate = result as
|
|
51
|
+
| {
|
|
52
|
+
rowsAffected?: unknown;
|
|
53
|
+
affectedRows?: unknown;
|
|
54
|
+
rowCount?: unknown;
|
|
55
|
+
count?: unknown;
|
|
56
|
+
changes?: unknown;
|
|
57
|
+
meta?: { changes?: unknown };
|
|
58
|
+
}
|
|
59
|
+
| undefined;
|
|
60
|
+
const value =
|
|
61
|
+
candidate?.rowsAffected ??
|
|
62
|
+
candidate?.affectedRows ??
|
|
63
|
+
candidate?.rowCount ??
|
|
64
|
+
candidate?.count ??
|
|
65
|
+
candidate?.changes ??
|
|
66
|
+
candidate?.meta?.changes;
|
|
67
|
+
return typeof value === "number" ? value : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
71
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parsePersistedDataRecord(
|
|
75
|
+
designId: string,
|
|
76
|
+
raw: string | null | undefined,
|
|
77
|
+
): Record<string, unknown> {
|
|
78
|
+
// Legacy rows may contain SQL NULL despite the current NOT NULL schema.
|
|
79
|
+
// Malformed/non-object non-null values are corruption, not an empty design:
|
|
80
|
+
// fail loud so a patch can never silently erase the unreadable payload.
|
|
81
|
+
if (raw == null) return {};
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(raw);
|
|
84
|
+
if (isRecord(parsed)) return parsed;
|
|
85
|
+
} catch {
|
|
86
|
+
// The dedicated error below explains why the mutation is refused.
|
|
87
|
+
}
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Design ${designId} has invalid data JSON. Refusing to overwrite it.`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseDataOperationRevisions(
|
|
94
|
+
designId: string,
|
|
95
|
+
raw: string | null | undefined,
|
|
96
|
+
): DataOperationRevisions {
|
|
97
|
+
if (raw == null || raw === "") return {};
|
|
98
|
+
try {
|
|
99
|
+
const parsed: unknown = JSON.parse(raw);
|
|
100
|
+
if (!isRecord(parsed)) throw new Error("not an object");
|
|
101
|
+
const revisions: DataOperationRevisions = {};
|
|
102
|
+
for (const [source, revision] of Object.entries(parsed)) {
|
|
103
|
+
if (
|
|
104
|
+
typeof revision !== "number" ||
|
|
105
|
+
!Number.isSafeInteger(revision) ||
|
|
106
|
+
revision < 0
|
|
107
|
+
) {
|
|
108
|
+
throw new Error("invalid revision");
|
|
109
|
+
}
|
|
110
|
+
revisions[source] = revision;
|
|
111
|
+
}
|
|
112
|
+
return revisions;
|
|
113
|
+
} catch {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Design ${designId} has invalid data operation revisions. Refusing an unordered write.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function withDataOperationRevision(
|
|
121
|
+
revisions: DataOperationRevisions,
|
|
122
|
+
source: string,
|
|
123
|
+
revision: number,
|
|
124
|
+
): DataOperationRevisions {
|
|
125
|
+
const next = { ...revisions };
|
|
126
|
+
// Refresh insertion order for the active source so the bounded record keeps
|
|
127
|
+
// recently active tabs and evicts abandoned sessions first.
|
|
128
|
+
delete next[source];
|
|
129
|
+
next[source] = revision;
|
|
130
|
+
while (Object.keys(next).length > MAX_DATA_OPERATION_SOURCES) {
|
|
131
|
+
const oldest = Object.keys(next)[0];
|
|
132
|
+
if (oldest === undefined) break;
|
|
133
|
+
delete next[oldest];
|
|
134
|
+
}
|
|
135
|
+
return next;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Apply path-addressed map operations without mutating the parsed source.
|
|
140
|
+
*
|
|
141
|
+
* This is intentionally not a generic recursive merge. A missing key can mean
|
|
142
|
+
* either "the caller read before a peer added it" or "delete this key", so
|
|
143
|
+
* inferring deletion from omission would resurrect or erase frames. Explicit
|
|
144
|
+
* set/delete operations keep both intents unambiguous and CAS-retryable.
|
|
145
|
+
*/
|
|
146
|
+
function applyDataOperations(
|
|
147
|
+
designId: string,
|
|
148
|
+
raw: string | null | undefined,
|
|
149
|
+
operations: DataOperation[],
|
|
150
|
+
): string {
|
|
151
|
+
const root: Record<string, unknown> = {
|
|
152
|
+
...parsePersistedDataRecord(designId, raw),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
for (const operation of operations) {
|
|
156
|
+
let target: Record<string, unknown> = root;
|
|
157
|
+
let missingDeleteParent = false;
|
|
158
|
+
const parentPath = operation.path.slice(0, -1);
|
|
159
|
+
for (const segment of parentPath) {
|
|
160
|
+
const value = target[segment];
|
|
161
|
+
if (value === undefined) {
|
|
162
|
+
if (operation.op === "delete") {
|
|
163
|
+
missingDeleteParent = true;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
const next: Record<string, unknown> = {};
|
|
167
|
+
target[segment] = next;
|
|
168
|
+
target = next;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (!isRecord(value)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Cannot apply design data operation through non-object path "${operation.path.join(".")}".`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const cloned = { ...value };
|
|
177
|
+
target[segment] = cloned;
|
|
178
|
+
target = cloned;
|
|
179
|
+
}
|
|
180
|
+
if (missingDeleteParent) continue;
|
|
181
|
+
|
|
182
|
+
const leaf = operation.path[operation.path.length - 1]!;
|
|
183
|
+
if (operation.op === "delete") {
|
|
184
|
+
delete target[leaf];
|
|
185
|
+
} else {
|
|
186
|
+
target[leaf] = operation.value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return JSON.stringify(root);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default defineAction({
|
|
194
|
+
description:
|
|
195
|
+
"Update an existing design project. Requires editor access. " +
|
|
196
|
+
"Only provided fields are updated; omitted fields are left unchanged. " +
|
|
197
|
+
"For map entries such as canvasFrames, use dataOperations " +
|
|
198
|
+
"with explicit set/delete paths instead of a full data snapshot.",
|
|
199
|
+
schema: z
|
|
200
|
+
.object({
|
|
201
|
+
id: z.string().describe("Design ID"),
|
|
202
|
+
title: z.string().optional().describe("New title"),
|
|
203
|
+
description: z.string().optional().describe("New description"),
|
|
204
|
+
data: z
|
|
205
|
+
.string()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe(
|
|
208
|
+
"Legacy partial JSON object snapshot. Concurrent conflicting snapshots are rejected; use dataOperations for map entries.",
|
|
209
|
+
),
|
|
210
|
+
dataOperations: z
|
|
211
|
+
.array(dataOperationSchema)
|
|
212
|
+
.min(1)
|
|
213
|
+
.max(500)
|
|
214
|
+
.optional()
|
|
215
|
+
.describe(
|
|
216
|
+
"Atomic path-addressed set/delete operations for design data. Safe to CAS-retry across concurrent writers.",
|
|
217
|
+
),
|
|
218
|
+
operationSource: z
|
|
219
|
+
.string()
|
|
220
|
+
.trim()
|
|
221
|
+
.min(1)
|
|
222
|
+
.max(128)
|
|
223
|
+
.optional()
|
|
224
|
+
.describe(
|
|
225
|
+
"Stable client-session id used with operationRevision to reject late out-of-order writes.",
|
|
226
|
+
),
|
|
227
|
+
operationRevision: z
|
|
228
|
+
.number()
|
|
229
|
+
.int()
|
|
230
|
+
.nonnegative()
|
|
231
|
+
.max(Number.MAX_SAFE_INTEGER)
|
|
232
|
+
.optional()
|
|
233
|
+
.describe(
|
|
234
|
+
"Monotonic sequence for operationSource. Stale or duplicate revisions are successful no-ops.",
|
|
235
|
+
),
|
|
236
|
+
projectType: z
|
|
237
|
+
.enum(["prototype", "other"])
|
|
238
|
+
.optional()
|
|
239
|
+
.describe("Updated project type"),
|
|
240
|
+
designSystemId: z
|
|
241
|
+
.string()
|
|
242
|
+
.min(1)
|
|
243
|
+
.nullable()
|
|
244
|
+
.optional()
|
|
245
|
+
.describe("Design system ID to link, or null to unlink"),
|
|
246
|
+
})
|
|
247
|
+
.refine(
|
|
248
|
+
({ data, dataOperations }) =>
|
|
249
|
+
data === undefined || dataOperations === undefined,
|
|
250
|
+
{
|
|
251
|
+
message: "Provide either data or dataOperations, not both.",
|
|
252
|
+
path: ["dataOperations"],
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
.superRefine((value, context) => {
|
|
256
|
+
const hasSource = value.operationSource !== undefined;
|
|
257
|
+
const hasRevision = value.operationRevision !== undefined;
|
|
258
|
+
if (hasSource !== hasRevision) {
|
|
259
|
+
context.addIssue({
|
|
260
|
+
code: "custom",
|
|
261
|
+
path: hasSource ? ["operationRevision"] : ["operationSource"],
|
|
262
|
+
message:
|
|
263
|
+
"operationSource and operationRevision must be provided together.",
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if ((hasSource || hasRevision) && !value.dataOperations) {
|
|
267
|
+
context.addIssue({
|
|
268
|
+
code: "custom",
|
|
269
|
+
path: ["dataOperations"],
|
|
270
|
+
message:
|
|
271
|
+
"operationSource and operationRevision require dataOperations.",
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}),
|
|
28
275
|
run: async ({
|
|
29
276
|
id,
|
|
30
277
|
title,
|
|
31
278
|
description,
|
|
32
279
|
data,
|
|
280
|
+
dataOperations,
|
|
281
|
+
operationSource,
|
|
282
|
+
operationRevision,
|
|
33
283
|
projectType,
|
|
34
284
|
designSystemId,
|
|
35
285
|
}) => {
|
|
@@ -47,57 +297,122 @@ export default defineAction({
|
|
|
47
297
|
}
|
|
48
298
|
|
|
49
299
|
const db = getDb();
|
|
50
|
-
const now = new Date().toISOString();
|
|
51
300
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
57
|
-
? (parsed as Record<string, unknown>)
|
|
58
|
-
: ({} as Record<string, unknown>);
|
|
59
|
-
} catch {
|
|
60
|
-
return {} as Record<string, unknown>;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Read-merge-write inside one transaction so a partial `data` update can't
|
|
65
|
-
// race a concurrent write and either destroy framework-owned keys (e.g.
|
|
66
|
-
// canvasFrames/tweaks) or overwrite newer server-side keys. Mirrors
|
|
67
|
-
// generate-design's transactional read-merge.
|
|
68
|
-
await db.transaction(async (tx) => {
|
|
69
|
-
const updates: Record<string, unknown> = { updatedAt: now };
|
|
301
|
+
const staticUpdates = (): Record<string, unknown> => {
|
|
302
|
+
const updates: Record<string, unknown> = {
|
|
303
|
+
updatedAt: new Date().toISOString(),
|
|
304
|
+
};
|
|
70
305
|
if (title !== undefined) updates.title = title;
|
|
71
306
|
if (description !== undefined) updates.description = description;
|
|
72
|
-
if (data !== undefined) {
|
|
73
|
-
const [existing] = await tx
|
|
74
|
-
.select({ data: schema.designs.data })
|
|
75
|
-
.from(schema.designs)
|
|
76
|
-
.where(eq(schema.designs.id, id));
|
|
77
|
-
const incomingParsed = JSON.parse(data);
|
|
78
|
-
if (
|
|
79
|
-
incomingParsed &&
|
|
80
|
-
typeof incomingParsed === "object" &&
|
|
81
|
-
!Array.isArray(incomingParsed)
|
|
82
|
-
) {
|
|
83
|
-
updates.data = JSON.stringify({
|
|
84
|
-
...asRecord(existing?.data),
|
|
85
|
-
...(incomingParsed as Record<string, unknown>),
|
|
86
|
-
});
|
|
87
|
-
} else {
|
|
88
|
-
// Non-object JSON (array/primitive): keep the original verbatim write.
|
|
89
|
-
updates.data = data;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
307
|
if (projectType !== undefined) updates.projectType = projectType;
|
|
93
308
|
if (designSystemId !== undefined) updates.designSystemId = designSystemId;
|
|
309
|
+
return updates;
|
|
310
|
+
};
|
|
94
311
|
|
|
95
|
-
|
|
312
|
+
if (data === undefined && dataOperations === undefined) {
|
|
313
|
+
await db
|
|
96
314
|
.update(schema.designs)
|
|
97
|
-
.set(
|
|
315
|
+
.set(staticUpdates())
|
|
316
|
+
.where(eq(schema.designs.id, id));
|
|
317
|
+
return { id, updated: true };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const maxAttempts = dataOperations ? MAX_DATA_CAS_ATTEMPTS : 1;
|
|
321
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
322
|
+
const [existing] = await db
|
|
323
|
+
.select({
|
|
324
|
+
data: schema.designs.data,
|
|
325
|
+
dataOperationRevisions: schema.designs.dataOperationRevisions,
|
|
326
|
+
})
|
|
327
|
+
.from(schema.designs)
|
|
98
328
|
.where(eq(schema.designs.id, id));
|
|
99
|
-
|
|
329
|
+
if (!existing) {
|
|
330
|
+
throw new Error(`Design not found: ${id}`);
|
|
331
|
+
}
|
|
100
332
|
|
|
101
|
-
|
|
333
|
+
let nextData: string;
|
|
334
|
+
let nextOperationRevisions: string | undefined;
|
|
335
|
+
if (dataOperations) {
|
|
336
|
+
if (operationSource !== undefined && operationRevision !== undefined) {
|
|
337
|
+
const revisions = parseDataOperationRevisions(
|
|
338
|
+
id,
|
|
339
|
+
existing.dataOperationRevisions,
|
|
340
|
+
);
|
|
341
|
+
if ((revisions[operationSource] ?? -1) >= operationRevision) {
|
|
342
|
+
return { id, updated: true, stale: true };
|
|
343
|
+
}
|
|
344
|
+
nextOperationRevisions = JSON.stringify(
|
|
345
|
+
withDataOperationRevision(
|
|
346
|
+
revisions,
|
|
347
|
+
operationSource,
|
|
348
|
+
operationRevision,
|
|
349
|
+
),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
nextData = applyDataOperations(id, existing.data, dataOperations);
|
|
353
|
+
} else {
|
|
354
|
+
const incomingParsed = JSON.parse(data!);
|
|
355
|
+
nextData = isRecord(incomingParsed)
|
|
356
|
+
? JSON.stringify({
|
|
357
|
+
...parsePersistedDataRecord(id, existing.data),
|
|
358
|
+
...incomingParsed,
|
|
359
|
+
})
|
|
360
|
+
: data!;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Compare-and-swap on the exact data snapshot. Transactions at the
|
|
364
|
+
// default isolation level do not make a read-merge-write safe: two
|
|
365
|
+
// transactions can both read the same JSON and the later UPDATE can
|
|
366
|
+
// overwrite the first. Explicit operations are safe to re-apply to the
|
|
367
|
+
// latest row; a legacy full snapshot is ambiguous, so a conflict fails
|
|
368
|
+
// loud instead of guessing whether missing nested keys mean stale data
|
|
369
|
+
// or intentional deletion.
|
|
370
|
+
const revisionCondition =
|
|
371
|
+
operationSource !== undefined
|
|
372
|
+
? existing.dataOperationRevisions == null
|
|
373
|
+
? isNull(schema.designs.dataOperationRevisions)
|
|
374
|
+
: eq(
|
|
375
|
+
schema.designs.dataOperationRevisions,
|
|
376
|
+
existing.dataOperationRevisions,
|
|
377
|
+
)
|
|
378
|
+
: undefined;
|
|
379
|
+
const updateResult = await db
|
|
380
|
+
.update(schema.designs)
|
|
381
|
+
.set({
|
|
382
|
+
...staticUpdates(),
|
|
383
|
+
data: nextData,
|
|
384
|
+
...(nextOperationRevisions === undefined
|
|
385
|
+
? {}
|
|
386
|
+
: { dataOperationRevisions: nextOperationRevisions }),
|
|
387
|
+
})
|
|
388
|
+
.where(
|
|
389
|
+
and(
|
|
390
|
+
eq(schema.designs.id, id),
|
|
391
|
+
existing.data == null
|
|
392
|
+
? isNull(schema.designs.data)
|
|
393
|
+
: eq(schema.designs.data, existing.data),
|
|
394
|
+
...(revisionCondition ? [revisionCondition] : []),
|
|
395
|
+
),
|
|
396
|
+
);
|
|
397
|
+
const affected = affectedRowCount(updateResult);
|
|
398
|
+
if (affected === undefined) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
"The database driver did not report an affected-row count for the design data update.",
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (affected > 0) {
|
|
405
|
+
return { id, updated: true };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (dataOperations) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Could not update design ${id} after ${MAX_DATA_CAS_ATTEMPTS} concurrent attempts. Re-read the design and retry.`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
throw new Error(
|
|
415
|
+
"Design data changed while this snapshot was being saved. Re-read the design and retry, or use dataOperations for path-addressed map edits.",
|
|
416
|
+
);
|
|
102
417
|
},
|
|
103
418
|
});
|